From 6ebae3dacdaca961537d9f99db4af375102c94b9 Mon Sep 17 00:00:00 2001 From: Henrique Gaspar Date: Mon, 31 Mar 2025 15:34:31 +0200 Subject: [PATCH] Revert "Dumping new version of web page" --- build/vessel.js | 5015 +++++++------ build/vessel.module.js | 2 - build/vessel.zip | Bin 44313 -> 0 bytes examples/libs/bootstrap.bundle.min.js | 8 +- examples/libs/bootstrap.js | 6341 ++++++----------- examples/libs/bootstrap.min.css | 10 +- examples/libs/color-modes.js | 80 - .../6383409a326255b7@214.js | 99 - .../LICENSE.txt | 429 -- .../README.md | 33 - .../chapter-1/6d6823c516e88279@392.js | 1918 ----- .../chapter-1/715b4c45cc45c3b3@223.js | 423 -- .../chapter-1/7764a40fe6b83ca1@437.js | 295 - .../chapter-1/LICENSE.txt | 429 -- .../chapter-1/README.md | 33 - .../chapter-1/index.html | 14 - .../chapter-1/index.js | 1 - .../chapter-1/inspector.css | 1 - .../chapter-1/package.json | 14 - .../chapter-1/runtime.js | 2 - .../chapter-2/LICENSE.txt | 429 -- .../chapter-2/README.md | 33 - .../chapter-2/c53e95309e85a5e2@344.js | 822 --- .../chapter-2/index.html | 14 - .../chapter-2/index.js | 1 - .../chapter-2/inspector.css | 1 - .../chapter-2/package.json | 14 - .../chapter-2/runtime.js | 2 - .../chapter-3/README.md | 33 - .../chapter-3/ffd9de1252b975ac@56.js | 57 - .../chapter-3/index.html | 14 - .../chapter-3/index.js | 1 - .../chapter-3/inspector.css | 1 - .../chapter-3/package.json | 14 - .../chapter-3/runtime.js | 2 - .../chapter-4/LICENSE.txt | 429 -- .../chapter-4/README.md | 33 - .../chapter-4/ee558559d24181fc@56.js | 48 - .../chapter-4/index.html | 14 - .../chapter-4/index.js | 1 - .../chapter-4/inspector.css | 1 - .../chapter-4/package.json | 14 - .../chapter-4/runtime.js | 2 - .../index.html | 14 - .../from_concept_to_simulation_intro/index.js | 1 - .../inspector.css | 1 - .../package.json | 14 - .../runtime.js | 2 - .../section-2-2/0ef08910361ca44c@286.js | 1887 ----- .../section-2-2/LICENSE.txt | 429 -- .../section-2-2/README.md | 33 - .../section-2-2/index.html | 14 - .../section-2-2/index.js | 1 - .../section-2-2/inspector.css | 1 - .../section-2-2/package.json | 14 - .../section-2-2/runtime.js | 2 - .../section-2-3/9eb8e36a1ec635ca@244.js | 1929 ----- .../section-2-3/LICENSE.txt | 429 -- .../section-2-3/README.md | 33 - .../section-2-3/files/image_one.png | Bin 152584 -> 0 bytes .../section-2-3/files/image_two.png | Bin 133793 -> 0 bytes .../section-2-3/index.html | 14 - .../section-2-3/index.js | 1 - .../section-2-3/inspector.css | 1 - .../section-2-3/package.json | 14 - .../section-2-3/runtime.js | 2 - .../section-2-4/LICENSE.txt | 429 -- .../section-2-4/README.md | 33 - .../section-2-4/b6441b3891937553@193.js | 1374 ---- .../section-2-4/index.html | 14 - .../section-2-4/index.js | 1 - .../section-2-4/inspector.css | 1 - .../section-2-4/package.json | 14 - .../section-2-4/runtime.js | 2 - .../section-2-5/LICENSE.txt | 429 -- .../section-2-5/README.md | 33 - .../section-2-5/fb258c96437db831@80.js | 887 --- .../section-2-5/files/jason_one.json | 4133 ----------- .../section-2-5/index.html | 14 - .../section-2-5/index.js | 1 - .../section-2-5/inspector.css | 1 - .../section-2-5/package.json | 14 - .../section-2-5/runtime.js | 2 - .../section-2-6/215095beab0a6558@361.js | 180 - .../section-2-6/LICENSE.txt | 429 -- .../section-2-6/README.md | 33 - .../section-2-6/index.html | 14 - .../section-2-6/index.js | 1 - .../section-2-6/inspector.css | 1 - .../section-2-6/package.json | 14 - .../section-2-6/runtime.js | 2 - .../section-3-1/7e431024fab3fa4d@469.js | 1097 --- .../section-3-1/README.md | 33 - .../section-3-1/index.html | 14 - .../section-3-1/index.js | 1 - .../section-3-1/inspector.css | 1 - .../section-3-1/package.json | 14 - .../section-3-1/runtime.js | 2 - .../section-3-2/9ebc01faf17c138f@487.js | 1446 ---- .../section-3-2/README.md | 33 - .../section-3-2/index.html | 14 - .../section-3-2/index.js | 1 - .../section-3-2/inspector.css | 1 - .../section-3-2/package.json | 14 - .../section-3-2/runtime.js | 2 - .../section-4-1/LICENSE.txt | 429 -- .../section-4-1/README.md | 33 - .../section-4-1/d6e39865ec0947c5@263.js | 263 - .../section-4-1/files/reference_system.jpeg | Bin 69648 -> 0 bytes .../section-4-1/index.html | 14 - .../section-4-1/index.js | 1 - .../section-4-1/inspector.css | 1 - .../section-4-1/package.json | 14 - .../section-4-1/runtime.js | 2 - .../section-4-2-pt-2/327a73d5a73f182e@193.js | 357 - .../section-4-2-pt-2/LICENSE.txt | 429 -- .../section-4-2-pt-2/README.md | 33 - .../section-4-2-pt-2/index.html | 14 - .../section-4-2-pt-2/index.js | 1 - .../section-4-2-pt-2/inspector.css | 1 - .../section-4-2-pt-2/package.json | 14 - .../section-4-2-pt-2/runtime.js | 2 - .../section-4-2/5efffa9b6c946c41@738.js | 1144 --- .../section-4-2/LICENSE.txt | 429 -- .../section-4-2/README.md | 33 - .../section-4-2/files/props.json | 11 - .../section-4-2/index.html | 14 - .../section-4-2/index.js | 1 - .../section-4-2/inspector.css | 1 - .../section-4-2/package.json | 14 - .../section-4-2/runtime.js | 2 - .../section-4-3/LICENSE.txt | 429 -- .../section-4-3/README.md | 33 - .../section-4-3/c4bf3b15fe6cdfaf@850.js | 811 --- .../files/gunnerus_power_plant.json | 27 - .../section-4-3/files/loop_render.png | Bin 157449 -> 0 bytes .../section-4-3/files/man_sample.json | 13 - .../section-4-3/index.html | 14 - .../section-4-3/index.js | 1 - .../section-4-3/inspector.css | 1 - .../section-4-3/package.json | 14 - .../section-4-3/runtime.js | 2 - .../README.md | 33 - .../d9622d94c6d81838@257.js | 1047 --- .../index.html | 14 - .../index.js | 1 - .../inspector.css | 1 - .../package.json | 14 - .../runtime.js | 2 - examples/sea_sick.html | 368 - images/Gunnerus_starboard_su.jpg | Bin 320120 -> 0 bytes images/brand_NTNU.svg | 37 - images/github-logo-svg.svg | 1 - images/mark-github.svg | 3 - index.html | 1339 ++-- package.json | 8 +- utils/build/rollup.config.js | 52 +- vessel.js | 3950 ---------- vessel.module.js | 3950 ---------- vessel.module.min.js | 3 - 160 files changed, 5013 insertions(+), 43104 deletions(-) delete mode 100644 build/vessel.zip delete mode 100644 examples/libs/color-modes.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/6383409a326255b7@214.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/LICENSE.txt delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/README.md delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-1/6d6823c516e88279@392.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-1/715b4c45cc45c3b3@223.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-1/7764a40fe6b83ca1@437.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-1/LICENSE.txt delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-1/README.md delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-1/index.html delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-1/index.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-1/inspector.css delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-1/package.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-1/runtime.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-2/LICENSE.txt delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-2/README.md delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-2/c53e95309e85a5e2@344.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-2/index.html delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-2/index.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-2/inspector.css delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-2/package.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-2/runtime.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-3/README.md delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-3/ffd9de1252b975ac@56.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-3/index.html delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-3/index.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-3/inspector.css delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-3/package.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-3/runtime.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-4/LICENSE.txt delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-4/README.md delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-4/ee558559d24181fc@56.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-4/index.html delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-4/index.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-4/inspector.css delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-4/package.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/chapter-4/runtime.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/index.html delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/index.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/inspector.css delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/package.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/runtime.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-2/0ef08910361ca44c@286.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-2/LICENSE.txt delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-2/README.md delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-2/index.html delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-2/index.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-2/inspector.css delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-2/package.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-2/runtime.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-3/9eb8e36a1ec635ca@244.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-3/LICENSE.txt delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-3/README.md delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-3/files/image_one.png delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-3/files/image_two.png delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-3/index.html delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-3/index.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-3/inspector.css delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-3/package.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-3/runtime.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-4/LICENSE.txt delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-4/README.md delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-4/b6441b3891937553@193.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-4/index.html delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-4/index.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-4/inspector.css delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-4/package.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-4/runtime.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-5/LICENSE.txt delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-5/README.md delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-5/fb258c96437db831@80.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-5/files/jason_one.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-5/index.html delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-5/index.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-5/inspector.css delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-5/package.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-5/runtime.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-6/215095beab0a6558@361.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-6/LICENSE.txt delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-6/README.md delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-6/index.html delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-6/index.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-6/inspector.css delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-6/package.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-2-6/runtime.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-3-1/7e431024fab3fa4d@469.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-3-1/README.md delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-3-1/index.html delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-3-1/index.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-3-1/inspector.css delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-3-1/package.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-3-1/runtime.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-3-2/9ebc01faf17c138f@487.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-3-2/README.md delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-3-2/index.html delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-3-2/index.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-3-2/inspector.css delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-3-2/package.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-3-2/runtime.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-1/LICENSE.txt delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-1/README.md delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-1/d6e39865ec0947c5@263.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-1/files/reference_system.jpeg delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-1/index.html delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-1/index.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-1/inspector.css delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-1/package.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-1/runtime.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-2-pt-2/327a73d5a73f182e@193.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-2-pt-2/LICENSE.txt delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-2-pt-2/README.md delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-2-pt-2/index.html delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-2-pt-2/index.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-2-pt-2/inspector.css delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-2-pt-2/package.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-2-pt-2/runtime.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-2/5efffa9b6c946c41@738.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-2/LICENSE.txt delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-2/README.md delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-2/files/props.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-2/index.html delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-2/index.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-2/inspector.css delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-2/package.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-2/runtime.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-3/LICENSE.txt delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-3/README.md delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-3/c4bf3b15fe6cdfaf@850.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-3/files/gunnerus_power_plant.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-3/files/loop_render.png delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-3/files/man_sample.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-3/index.html delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-3/index.js delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-3/inspector.css delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-3/package.json delete mode 100644 examples/observable_examples/from_concept_to_simulation_intro/section-4-3/runtime.js delete mode 100644 examples/observable_examples/pendulum-load-in-lifting-operation/README.md delete mode 100644 examples/observable_examples/pendulum-load-in-lifting-operation/d9622d94c6d81838@257.js delete mode 100644 examples/observable_examples/pendulum-load-in-lifting-operation/index.html delete mode 100644 examples/observable_examples/pendulum-load-in-lifting-operation/index.js delete mode 100644 examples/observable_examples/pendulum-load-in-lifting-operation/inspector.css delete mode 100644 examples/observable_examples/pendulum-load-in-lifting-operation/package.json delete mode 100644 examples/observable_examples/pendulum-load-in-lifting-operation/runtime.js delete mode 100644 examples/sea_sick.html delete mode 100644 images/Gunnerus_starboard_su.jpg delete mode 100644 images/brand_NTNU.svg delete mode 100644 images/github-logo-svg.svg delete mode 100644 images/mark-github.svg delete mode 100644 vessel.js delete mode 100644 vessel.module.js delete mode 100644 vessel.module.min.js diff --git a/build/vessel.js b/build/vessel.js index 42fda36..146fc89 100644 --- a/build/vessel.js +++ b/build/vessel.js @@ -1,95 +1,153 @@ -const REVISION = "v0.14-alpha"; +//vessel.js library, built 2023-08-05 18:10:42.035919 +/* +Import like this in HTML: + +Then in javascript use classes and functions with a vessel prefix. Example: +let ship = new vessel.Ship(someSpecification); +*/ + +"use strict"; +var vessel = {}; +var Vessel = vessel; //alias for backwards compatibility + +(function() { //@EliasHasle -/*Base class for objects that are constructed from -a literal object. +//Some small helpers for operations on 3D vectors +//A vector is simply defined as an object with properties x,y,z. -Constructors can take more parameters than the specification, but the specification must be the first parameter. +var Vectors = { + clone: function ( v ) { -setFromSpecification will typically be overridden by derived classes. Overriding implementations will typically do some sanity checking. + return { x: v.x, y: v.y, z: v.z }; -getSpecification will also typically be overridden. The default implementation here is just a sketch. Maybe not even correct for the simplest subclasses. + }, -Maybe this can be improved by implementing fromJSON and to toJSON methods. -*/ + scale: function ( v, s ) { + + return { x: s * v.x, y: s * v.y, z: s * v.z }; -class JSONSpecObject { + }, - constructor( specification, baseObjects ) { + norm: function ( v ) { - if ( specification === null ) { + return Math.sqrt( v.x ** 2 + v.y ** 2 + v.z ** 2 ); - console.warn( "JSONSpecObject: null specification provided. Defaulting to empty specification." ); - specification = {}; + }, - } else if ( typeof specification === "object" ) ; else { + normalize: function ( v ) { - /*else if (typeof specification === "string") { - try { - specification = JSON.parse(specification); - } catch(e) { - console.error("JSONSpecObject: "+ e.message + "\n Defaulting to empty specification."); - specification = {}; - } - }*/ - if ( typeof specification !== "undefined" ) { + let l = norm( v ); + return { x: v.x / l, y: v.y / l, z: v.z / l }; - console.error( "JSONSpecObject: Invalid constructor parameter. Defaulting to empty specification." ); + }, - } + normSquared: function ( v ) { - specification = {}; + return v.x ** 2 + v.y ** 2 + v.z ** 2; - } + }, - if ( baseObjects ) { + /*Adds two or more vectors given as individual parameters, + and returns a new vector that is the component-wise + sum of the input vectors.*/ + add: function ( u, v, ...rest ) { + + if ( rest.length > 0 ) return Vectors.sum( [ u, v ] + rest ); + return { x: u.x + v.x, y: u.y + v.y, z: u.z + v.z }; + + }, + + //Takes an array of vectors as input, and returns a new vector + //that is the component-wise sum of the input vectors. + sum: function ( vectors ) { + + let S = { x: 0, y: 0, z: 0 }; + for ( let i = 0; i < vectors.length; i ++ ) { - this.baseObjects = baseObjects; + let v = vectors[ i ]; + S.x += v.x; + S.y += v.y; + S.z += v.z; } - this.setFromSpecification( specification ); + return S; - } + }, + + //Takes two vector parameters u,v, and returns the vector u-v. + sub: function ( u, v ) { - setFromSpecification( specification ) { + //return Vectors.add(u, Vectors.scale(v, -1)); //equivalent + return { x: u.x - v.x, y: u.y - v.y, z: u.z - v.z }; - //No sanity checking by default. - Object.assign( this, specification ); - return this; + }, - } + dot: function ( u, v ) { - getSpecification() { + return u.x * v.x + u.y * v.y + u.z * v.z; - let spec = {}; - for ( k of Object.keys( this ) ) { + }, - if ( this.hasOwnProperty( k ) ) spec[ k ] = this[ k ]; + cross: function ( u, v ) { - } + return { + x: u.y * v.z - u.z * v.y, + y: u.z * v.x - u.x * v.z, + z: u.x * v.y - u.y * v.x + }; - return spec; + }, - } + mulComponents: function ( u, v ) { - //toJSON is the standard way. Added here for testing. - toJSON() { + return { + x: u.x * v.x, + y: u.y * v.y, + z: u.z * v.z + }; - return this.getSpecification(); + }, - } + //Return the result of rotating the vector v by angles r={x,y,z} in radians. + //Intrinsic ZYX order (yaw,pitch,roll) is achieved by applying world axis rotations in XYZ order. + rotateTaitBryan: function ( v, r ) { - //fromJSON is added as an alternative and better name. - fromJSON( spec ) { + let c, s; + + //Rotate around x axis + c = Math.cos( r.x ); + s = Math.sin( r.x ); + v = { + x: v.x, + y: v.y * c - v.z * s, + z: v.y * s + v.z * c + }; - return this.setFromSpecification( spec ); + //Then around y axis + c = Math.cos( r.y ); + s = Math.sin( r.y ); + v = { + x: v.z * s + v.x * c, + y: v.y, + z: v.z * c - v.x * s + }; - } + //Then around z axis + c = Math.cos( r.z ); + s = Math.sin( r.z ); + v = { + x: v.x * c - v.y * s, + y: v.x * s + v.y * c, + z: v.z + }; -} + return v; + } +}; //@EliasHasle //Some interpolation helpers. Only linear and bilinear for now. @@ -151,6 +209,25 @@ function linearFromArrays( xx, yy, x ) { } +//Source: https://en.wikipedia.org/wiki/Bilinear_interpolation +//(I have used other sources too) +function bilinearUnitSquareCoeffs( z00, z01, z10, z11 ) { + + let a00 = z00; //mux=muy=0 + let a10 = z10 - z00; //mux=1, muy=0 + let a01 = z01 - z00; //mux=0, muy=1 + let a11 = z11 + z00 - z01 - z10; //mux=muy=1 + return [ a00, a10, a01, a11 ]; + +} + +function bilinearUnitSquare( z00, z01, z10, z11, mux, muy ) { + + let [ a00, a10, a01, a11 ] = bilinearUnitSquareCoeffs( z00, z01, z10, z11 ); + return a00 + a10 * mux + a01 * muy + a11 * mux * muy; + +} + //Find coefficients for 1, x, y, xy. //This doesn't yet handle zero-lengths well. function bilinearCoeffs( x1, x2, y1, y2, z00, z01, z10, z11 ) { @@ -199,1373 +276,750 @@ function bilinear( x1, x2, y1, y2, z11, z12, z21, z22, x, y ) { return fromCoeffs; } +//@EliasHasle -class BaseObject extends JSONSpecObject { - - constructor( specification ) { - - super( specification ); - this.weightCache = {}; +//All inputs are numbers. The axes are given by a single coordinate. +function steiner(I, A, sourceAxis, targetAxis) { + return I + A * (sourceAxis - targetAxis) ** 2; +} +//Calculate area, center, Ix, Iy. +function trapezoidCalculation(xbase0, xbase1, xtop0, xtop1, ybase, ytop) { + let a = xbase1 - xbase0; + let b = xtop1 - xtop0; + let h = ytop - ybase; + if (a < 0 || b < 0 || h < 0) { + console.warn("trapezoidCalculation: Unsupported input. Possibly not a valid trapezoid."); } + let A = 0.5 * (a + b) * h; + let yc = (a == 0 && b == 0) ? ybase + 0.5 * h : ybase + h * (2 * a + b) / (3 * (a + b)); + let d = xbase0 + 0.5 * a; //shorthand + let xc = h === 0 ? 0.25 * (xbase0 + xbase1 + xtop0 + xtop1) : d + (xtop0 + 0.5 * b - d) * (yc - ybase) / h; + let Ix = (a == 0 && b == 0) ? 0 : h ** 3 * (a ** 2 + 4 * a * b + b ** 2) / (36 * (a + b)); - setFromSpecification( spec ) { - - this.id = spec.id; - this.affiliations = spec.affiliations || {}; - this.boxDimensions = spec.boxDimensions || { length: undefined, width: undefined, height: undefined }; - this.weightInformation = spec.weightInformation; - this.cost = spec.cost || { currency: undefined, value: undefined }; - this.capabilities = spec.capabilities || {}; - this.file3D = spec.file3D; - this.baseState = spec.baseState; - return this; + //For Iy I must decompose (I think negative results will work fine): + let Art1 = 0.5 * (xtop0 - xbase0) * h; + let xcrt1 = xbase0 + (xtop0 - xbase0) / 3; + let Iyrt1 = (xtop0 - xbase0) ** 3 * h / 36; + let Arec = (xbase1 - xtop0) * h; + let xcrec = 0.5 * (xtop0 + xbase1); + let Iyrec = (xbase1 - xtop0) ** 3 * h / 12; + let Art2 = 0.5 * (xbase1 - xtop1) * h; + let xcrt2 = (xtop1 + (xbase1 - xtop1) / 3); + let Iyrt2 = (xbase1 - xtop1) ** 3 * h / 36; + + let Iy = steiner(Iyrt1, Art1, xcrt1, xc) + + steiner(Iyrec, Arec, xcrec, xc) + + steiner(Iyrt2, Art2, xcrt2, xc); + + let maxX = Math.max.apply(null, [xbase0, xbase1, xtop0, xtop1]); + let minX = Math.min.apply(null, [xbase0, xbase1, xtop0, xtop1]); + let maxY = Math.max(ybase, ytop); + let minY = Math.min(ybase, ytop); + + return {A: A, xc: xc, yc: yc, Ix: Ix, Iy: Iy, maxX: maxX, minX: minX, maxY: maxY, minY: minY}; +} +function combineAreas(array) { + let A = 0 + let xc = 0 + let yc = 0 + let maxX = 0, + minX = 0, + maxY = 0, + minY = 0 + let L = array.length + let foundMinY = false + let foundMaxY = false + for (let i = 0; i < L; i++) { + let e = array[i] + A += e.A + xc += e.xc * e.A + yc += e.yc * e.A + if (!isNaN(e.maxX) && e.maxX > maxX) maxX = e.maxX + if (!isNaN(e.minX) && e.minX < minX) minX = e.minX + if (!isNaN(e.maxY) && e.maxY > maxY && !foundMaxY && foundMinY) { + maxY = e.maxY + if (e.A === 0) { + foundMaxY = true + } + } + if (!isNaN(e.minY) && !foundMinY) { + if (e.A !== 0) { + minY = e.minY + foundMinY = true + } + } } + if (!foundMaxY) maxY = array[L-1].maxY //if foundMaxY is false then the ship is a barge and a different logic must apply @ferrari212 - getSpecification() { + let Ix = 0 + let Iy = 0 - return { - id: this.id, - affiliations: this.affiliations, - boxDimensions: this.boxDimensions, - weightInformation: this.weightInformation, - cost: this.cost, - capabilities: this.capabilities, - file3D: this.file3D, - baseState: this.baseState - }; + if (A !== 0) { + xc /= A + yc /= A + } else { + //console.warn("Zero area combination."); + //console.trace(); + xc /= L + yc /= L + } + for (let i = 0; i < array.length; i++) { + let e = array[i] + Ix += steiner(e.Ix, e.A, e.yc, yc) + Iy += steiner(e.Iy, e.A, e.xc, xc) } - //Maybe this will take more state parameters than just fullness. - getWeight( fullness ) { + return { A: A, xc: xc, yc: yc, Ix: Ix, Iy: Iy, maxX: maxX, minX: minX, maxY: maxY, minY: minY } +} - fullness = fullness || 0; +//x and y here refers to coordinates in the plane that is being calculated on. +function sectionCalculation({xs, ymins, ymaxs}) { + //console.group/*Collapsed*/("sectionCalculation"); + //console.info("Arguments (xs, ymins, ymaxs): ", arguments[0]); - let wi = this.weightInformation; - //Should maybe have been this.capabilities.weightInformation? + let calculations = []; + for (let i = 0; i < xs.length - 1; i++) { + let xbase = xs[i]; + let xtop = xs[i + 1]; + let ybase0 = ymins[i] || 0; + let ybase1 = ymaxs[i] || 0; + let ytop0 = ymins[i + 1] || 0; + let ytop1 = ymaxs[i + 1] || 0; - //(Fluid) container properties default to no content: - let d = wi.contentDensity || 0; - let v = wi.volumeCapacity || 0; - //Maybe we should have another handling of cargo (with variable density) + calculations.push(trapezoidCalculation(ybase0, ybase1, ytop0, ytop1, xbase, xtop)); + } - let m = wi.lightweight + d * v * fullness; - let cg; - if ( wi.fullnessCGMapping !== undefined ) { + let C = combineAreas(calculations); //Might be zero areas! - let fcgm = wi.fullnessCGMapping; - let fs = fcgm.fullnesses; - let cgs = fcgm.cgs; - //Find closest entries: - let { index: i, mu: mu } = bisectionSearch( fs, fullness ); - cg = []; - for ( let j = 0; j < 3; j ++ ) { + let output = {A: C.A, maxX: C.maxY, minX: C.minY, maxY: C.maxX, minY: C.minX, xc: C.yc, yc: C.xc, Ix: C.Iy, Iy: C.Ix, Abb: (C.maxY - C.minY) * (C.maxX - C.minX)}; + //console.info("Output: ", output); + //console.groupEnd(); + return output; +} - let c; - if ( i < fs.length - 1 ) - //Linear interpolation between closest entries: - c = lerp( cgs[ i ][ j ], cgs[ i + 1 ][ j ], mu ); - else c = cgs[ i ][ j ]; - //if (c===null || isNaN(c)) console.error("BaseObject.getWeight: Invalid value found after interpolation."); - cg.push( c ); +//For wetted area. I think this is right, but it is not tested. +//The numerical integral will not scale well with larger geometries. +//Then the full analytical solution is needed. +function bilinearArea(x1, x2, y1, y2, z11, z12, z21, z22, segs = 5) { + let [b00, b10, b01, b11] = bilinearCoeffs(x1, x2, y1, y2, z11, z12, z21, z22); + /* + z(x,y) = b00 + b10*x + b01*y + b11*xy + Partial derivative in x: b10 + b11*y + Partial derivative in y: b01 + b11*x + I think this will be right: + Tx(y) = (1, 0, b10+b11*y) + Ty(x) = (0, 1, b01+b11*x) + Then: + Tx X Ty = (-(b10+b11*y), -(b01+b11*x), 1) + |Tx X Ty| = sqrt((b10+b11*y)^2 + (b01+b11*x)^2 + 1) - } + Now, to get the area I need to integrate |Tx X Ty| over X,Y. - } else if ( wi.cg !== undefined ) { + Wolfram Alpha gave me this for the inner integral using x (indefinite): + integral sqrt((b01 + b11 x)^2 + 1 + (b10+b11*y)^2) dx = ((b01 + b11*x) sqrt((b01 + b11*x)^2 + 1 + (b10+b11*y)^2) + (1 + (b10+b11*y)^2)*ln(sqrt((b01 + b11*x)^2 + 1 + (b10+b11*y)^2) + b01 + b11*x))/(2*b11) + constant + That means this for the definite integral: + ((b01 + b11*x2)*sqrt((b01 + b11*x2)^2 + 1 + (b10+b11*y)^2) + 1 + (b10+b11*y)^2*ln(sqrt((b01 + b11*x2)^2 + 1 + (b10+b11*y)^2) + b01 + b11*x2))/(2*b11) - ((b01 + b11*x1)*sqrt((b01 + b11*x1)^2 + 1 + (b10+b11*y)^2) + (1 + (b10+b11*y)^2)*ln(sqrt((b01 + b11*x1)^2 + 1 + (b10+b11*y)^2) + b01 + b11*x1))/(2*b11) + = + (b01 + b11*x2)*sqrt((b01 + b11*x2)^2 + 1 + (b10+b11*y)^2)/(2*b11) + +(1 + (b10+b11*y)^2)*ln(sqrt((b01 + b11*x2)^2 + 1 + (b10+b11*y)^2) + b01 + b11*x2)/(2*b11)) + - (b01 + b11*x1)*sqrt((b01 + b11*x1)^2 + 1 + (b10+b11*y)^2)/(2*b11) + - (1 + (b10+b11*y)^2)*ln(sqrt((b01 + b11*x1)^2 + 1 + (b10+b11*y)^2) + b01 + b11*x1)/(2*b11) + = + (b01 + b11*x2)*sqrt((b01 + b11*x2)^2 + 1 + (b10+b11*y)^2)/(2*b11) + - (b01 + b11*x1)*sqrt((b01 + b11*x1)^2 + 1 + (b10+b11*y)^2)/(2*b11) + +(1 + (b10+b11*y)^2)*ln(sqrt((b01 + b11*x2)^2 + 1 + (b10+b11*y)^2) + b01 + b11*x2)/(2*b11)) + - (1 + (b10+b11*y)^2)*ln(sqrt((b01 + b11*x1)^2 + 1 + (b10+b11*y)^2) + b01 + b11*x1)/(2*b11) - //console.log("BaseObject.getWeight: Using specified cg."); - cg = wi.cg; + The two first integrals are similar, and the two last are also similar. With A=+-(b01 + b11*xi)/(2*b11), B=(b01 + b11*xi)^2+1, C=b10 and D=b11 (where xi represents either x1 or x2, and +- represents + for x2 and - for x1), I can calculate the integral of sqrt(B+(C+D*y)^2) and multiply by A. That integral is on the same form as the first one. - } else { + The two last integrals can be represented by setting A=+-1/(2*b11), B=(b01 + b11*xi)^2+1, C=b01+b11*xi, D=b10, E=b11, and calculating the integral of (1+(D+E*y)^2)*ln(sqrt(B+(D+E*y)^2)+C), and multiplying by A. + Here is the integration result from Wolfram Alpha: + integral(1 + (D + E y)^2) log(sqrt(B + (D + E y)^2) + C) dy = (-(6 (B^2 - 2 B C^2 - 3 B + C^4 + 3 C^2) tan^(-1)((D + E y)/sqrt(B - C^2)))/sqrt(B - C^2) + (6 (B^2 - 2 B C^2 - 3 B + C^4 + 3 C^2) tan^(-1)((C (D + E y))/(sqrt(B - C^2) sqrt(B + (D + E y)^2))))/sqrt(B - C^2) + 6 (B - C^2 - 3) (D + E y) + 3 C (-3 B + 2 C^2 + 6) log(sqrt(B + (D + E y)^2) + D + E y) + 3 C (D + E y) sqrt(B + (D + E y)^2) + 6 ((D + E y)^2 + 3) (D + E y) log(sqrt(B + (D + E y)^2) + C) - 2 (D + E y)^3)/(18 E) + constant - console.warn( "BaseObject.getWeight: No cg or fullnessCGMapping supplied. Defaults to center of bounding box." ); - cg = [ 0, 0, 0.5 * this.boxDimensions.height ]; + I am glad I did not try to do this by hand. But combining these formulae, we can get an exact integral of the area of a bilinear patch. Later. Bilinear patches are not an exact representation anyway. We may opt for something else. + */ + //Simple numerical calculation of double integral: + let A = 0; + let X = x2 - x1, Y = y2 - y1; + let N = segs, M = segs; + for (let i = 0; i < N; i++) { + let x = x1 + ((i + 0.5) / N) * X; + for (let j = 0; j < M; j++) { + let y = y1 + ((j + 0.5) / M) * Y; + A += Math.sqrt((b10 + b11 * y) ** 2 + (b01 + b11 * x) ** 2 + 1); } + } + A *= X * Y / (N * M); //dx dy - let w = { mass: m, cg: { x: cg[ 0 ], y: cg[ 1 ], z: cg[ 2 ] } }; - return w; - - } + return A; +} +/*Calculates the (arithmetic) average of the area of the two possible triangulations of the quad element (using two triangles). +This requires the base of the quad to be convex. If the base is arrowhead shaped, +The calculation will fail in undefined ways. +*/ +function elementArea(v1, v2, v3, v4) { + let A1 = Math.abs(signedTriangleArea(v1, v2, v3)) + Math.abs(signedTriangleArea(v3, v4, v1)); + let A2 = Math.abs(signedTriangleArea(v2, v3, v4)) + Math.abs(signedTriangleArea(v4, v1, v2)); + let A = 0.5 * (A1 + A2); + return A; } -//@EliasHasle +function signedTriangleArea(v1, v2, v3) { + let u = Vectors.sub(v2, v1); + let v = Vectors.sub(v3, v1); + let c = Vectors.cross(u, v); + let A = 0.5 * Vectors.norm(c); + return A; +}//@EliasHasle -//Some small helpers for operations on 3D vectors -//A vector is simply defined as an object with properties x,y,z. +//I have been doing some tests here of a simplified calculation. +//The results so far indicate that, for the prism hull, the results are almost identical, except that with the simple calculation the center of volume is almost right (but wrong enough to disqualify such a simple calculation). +/*Note that the coordinate system used here has xy as a grid, with z as heights on the grid, but in the intended application, which is calculations on transverse hull offsets, this z corresponds to the vessel y axis, and y corresponds to the vessel z axis. In any application of this function, the conversion between coordinate systems must be taken care of appropriately.*/ +// xy +function patchColumnCalculation( x1, x2, y1, y2, z00, z01, z10, z11 ) { -const Vectors = { - clone: function ( v ) { + //VOLUME: + //Analysis based on a bilinear patch: + // /* + // From here I call mux for x, and muy for y. + // Integral over unit square: + // INT[x from 0 to 1, INT[y from 0 to 1, (a00 + a10*x + a01*y + a11*x*y) dy] dx] + // = INT[x from 0 to 1, (a00+a10*x+0.5*a01+0.5*a11*x) dx] + // = a00 + 0.5*a10 + 0.5*a01 + 0.25*a11 + // Note that by expanding a00,a10,a01,a11, it is demonstrated that this (rather unsurprisingly) is exactly equivalent to taking the average z offset of the control points. + // */ + let X = x2 - x1; + let Y = y2 - y1; + let Ab = X * Y; //area of base of patch column + //let zAvg = (a00 + 0.5*a10 + 0.5*a01 + 0.25*a11); + let zAvg = 0.25 * ( z00 + z01 + z10 + z11 ); //equivalent + let V = Math.abs( Ab * zAvg ); //works - return { x: v.x, y: v.y, z: v.z }; + //CENTER OF VOLUME + let zc = 0.5 * zAvg; - }, + //Very approximate center of volume + //(does not account for different signs on z values, + //but that should be OK for hull offsets) + //let xc = (x1*(z00+z01)+x2*(z10+z11))/((z00+z01+z10+z11) || 1); + //let yc = (y1*(z00+z10)+y2*(z01+z11))/((z00+z01+z10+z11) || 1); - scale: function ( v, s ) { + // /* + // To find xc properly, I need to integrate x*z over the unit square, divide by zAvg(?) and scale and translate to ship coordinates afterwards: + // INT[x from 0 to 1, INT[y from 0 to 1, x*(a00 + a10*x + a01*y + a11*x*y) dy] dx] = + // INT[x from 0 to 1, INT[y from 0 to 1, (a00*x + a10*x^2 + a01*xy + a11*x^2*y) dy] dx] = + // INT[x from 0 to 1, (a00*x + a10*x^2 + 0.5*a01*x + 0.5*a11*x^2) dx] + // = (0.5*a00 + a10/3 + 0.25*a01 + a11/6) + //Trying to expand the coeffs to original z offsets: + // = (0.5*z00 + (z10-z00)/3 + 0.25*(z01-z00) + (z00+z00-z01-z10)/6) + // = ((1/12)*z00 + (1/6)*z10 + (1/12)*z01 + (1/6)*z00) + //Divide by zAvg to get muxc, then scale and translate to xc. + let xc = x1 + X * ( ( ( 1 / 12 ) * z00 + ( 1 / 6 ) * z10 + ( 1 / 12 ) * z01 + ( 1 / 6 ) * z11 ) / ( zAvg || 1 ) ); + //console.log("x1=%.2f, X=%.2f, muxc = %.2f", x1, X, (((1/12)*z00 + (1/6)*z10 + (1/12)*z01 + (1/6)*z11) / (zAvg || 1))); + //Similar for yc (modified symmetrically) + let yc = y1 + Y * ( ( ( 1 / 12 ) * z00 + ( 1 / 12 ) * z10 + ( 1 / 6 ) * z01 + ( 1 / 6 ) * z11 ) / ( zAvg || 1 ) ); + let [ a00, a10, a01, a11 ] = bilinearUnitSquareCoeffs( z00, z01, z10, z11 ); - return { x: s * v.x, y: s * v.y, z: s * v.z }; + //console.log("Patch column Cv = (%.2f, %.2f, %.2f)", xc,yc,zc); - }, + //AREA + //These two methods give very similar results, within about 1% difference for the fishing boat hull (used in PX121.json). + //Simple triangle average approximation for area (works) + /*let As = elementArea( + {x: x1, y: y1, z: z00}, + {x: x1, y: y2, z: z01}, + {x: x2, y: y1, z: z10}, + {x: x2, y: y2, z: z11});*/ + //Bilinear area calculation. Works too, but is currently numerical, and quite complex (which means it is bug-prone and hard to maintain). But it is more exact, even with just a few segments for numerical integration (the last, optional, parameter) + let As = Math.abs( bilinearArea( x1, x2, y1, y2, z00, z01, z10, z11 ) ); - norm: function ( v ) { + return { Ab: Ab, As: As, V: V, Cv: { x: xc, y: yc, z: zc } }; - return Math.sqrt( v.x ** 2 + v.y ** 2 + v.z ** 2 ); +} - }, +//Input: array of objects with calculation results for elements. +//Output: the combined results. +function combineVolumes( array ) { - normalize: function ( v ) { + let V = 0; + let As = 0; + let Cv = { x: 0, y: 0, z: 0 }; + let L = array.length; + //if (L===0) return {V,As,Cv}; + for ( let i = 0; i < L; i ++ ) { - let l = norm( v ); - return { x: v.x / l, y: v.y / l, z: v.z / l }; + let e = array[ i ]; + V += e.V; + As += e.As; //typically wetted area + //console.log(e.Cv); + Cv = Vectors.add( Cv, Vectors.scale( e.Cv, e.V ) ); - }, + } - normSquared: function ( v ) { + Cv = Vectors.scale( Cv, 1 / ( V || L || 1 ) ); - return v.x ** 2 + v.y ** 2 + v.z ** 2; + //console.info("combineVolumes: Combined Cv is (" + Cv.x + ", " + Cv.y + ", " + Cv.z + ")."); - }, + return { V, As, Cv };//{V: V, As: As, Cv: Cv}; - /*Adds two or more vectors given as individual parameters, - and returns a new vector that is the component-wise - sum of the input vectors.*/ - add: function ( u, v, ...rest ) { +} +//@MrEranwe +//@EliasHasle - if ( rest.length > 0 ) return Vectors.sum( [ u, v ] + rest ); - return { x: u.x + v.x, y: u.y + v.y, z: u.z + v.z }; +"use strict"; +//Elias notes: +//LCB and LCG were obviously considered in another coordinate system than we are using. I have corrected this, assuming that the wrong coordinate system had the origin centered longitudinally. +//The hull mass is off by several orders of magnitude. Checking the paper, it seems likely that the "typical" K parameters are aimed at producing units of tonnes, not kg. +//It is not mathematically correct to strip down the structural weight calculation the way it is done here, because the exponentiation (E^1.36) cannot be simply decomposed as a sum of exponentiated terms (with the same exponent). +//Elias has only reviewed and modified the hull weight calculation. - }, +// This function estimates the structural weight of the hull. This includes the weight of the basic hull to its depth amidships. - //Takes an array of vectors as input, and returns a new vector - //that is the component-wise sum of the input vectors. - sum: function ( vectors ) { +// It is based on Watson and Gilfillan modeling approach using a specific modification of the Lloyd’s Equipment Numeral E as the independent variable. +// +// +// Inputs +// K is the structural weight coefficient. Parsons, chapter 11, table 11.VII. +// L is LWL or LBP +// B is molded beam +// T is molded draft +// D is molded depth +// Superstructures and shiphouses are not being considered in the weight +// CB is the block coefficient +// LCB is the Longitudinal Center of Bouyancy +// +// Return +// It returns an object on the format {mass:1234, cg: {x:4,y:3,z:2}}, where the unit of mass is unclear, and x,y,z is in meters from aft,center,bottom, respectively. - let S = { x: 0, y: 0, z: 0 }; - for ( let i = 0; i < vectors.length; i ++ ) { +function parametricWeightHull(K, L, B, T, D, CB, Fn) { - let v = vectors[ i ]; - S.x += v.x; - S.y += v.y; - S.z += v.z; + // Calculates estimated structural weight + // E is the Lloyd’s Equipment Numeral + let E = L * (B + T) + 0.85 * L * (D - T); + // CbCorrected is the estimated corrected block coefficient + let CBCorrected = CB + (1 - CB) * ((0.8 * D - T) / (3 * T)); + // W is the estimated structural weight + let W = K * Math.pow(E, 1.36) * (1 + 0.5 * (CBCorrected - 0.7)); - } + // Calculates LCG and VCG + // VCGHull is the Vertical Center of Gravity of the hull + let VCGHull = 0; + if (L < 120) { + VCGHull = 0.01 * D * (46.6 + 0.135 * (0.81 - CB) * Math.pow(L / D, 2)) + 0.008 * D * (L / D - 6.5); + } + else { + VCGHull = 0.01 * D * (46.6 + 0.135 * (0.81 - CB) * Math.pow(L / D, 2)); + } + // LCB is the longitudinal Center of Buoyancy converted from + // percentage plus forward of amidships to meters from aft + let LCB = Fn ? 0.5 * L + (9.7 - 45 * Fn) * L / 100 : L * 0.516; + // LCGHull is the Longitudinal Center of Gravity of the hull in meters from aft + let LCGHull = LCB - 0.15 * L / 100; - return S; + // Returns the object - }, + return {mass: W, cg: {x: LCGHull, y: 0, z: VCGHull}}; +} - //Takes two vector parameters u,v, and returns the vector u-v. - sub: function ( u, v ) { +// This function estimates the remainder of the Dead Ship Weight. It includes the fuel, the lube oil, the fresh water, the crew and the provisions and stores. +// +// +// Inputs +// Co is the outfit weight coefficient. Parsons Chapter 11 Figure 11.17. Pag 24. +// LBP is the Lenght Between Perpendiculars. +// D is molded depth +// +// Return +// It returns an object with the properties mass. - //return Vectors.add(u, Vectors.scale(v, -1)); //equivalent - return { x: u.x - v.x, y: u.y - v.y, z: u.z - v.z }; +function parametricWeightDeadweight(SFR, MCR, speed, person, day) { - }, + // Calculates estimated weight + let Wfo = SFR * MCR * speed * 1.1; // Fuel oil Weight + let Wlo = 0; // Lube oil Weight + if (speed > 10) { + Wlo = 15; + } + else { + Wlo = 20 + } + let Wfw = 0.17 * person; // Weight of fresh water + let Wce = 0.17 * person; // Weight of crew and effects + let Wpr = 0.01 * person * day; // Weight of provisions and stores + let W = Wfo + Wlo + Wfw + Wce + Wpr; // Total weigth - dot: function ( u, v ) { - return u.x * v.x + u.y * v.y + u.z * v.z; + // VCGOut is the Vertical Center of Gravity of the Deadweight. Depends on designer + // LCGOut is the Longitudinal Center of Gravity of the Deadweight. Depends on designer - }, + // Returns the object - cross: function ( u, v ) { + return {mass: W}; +} - return { - x: u.y * v.z - u.z * v.y, - y: u.z * v.x - u.x * v.z, - z: u.x * v.y - u.y * v.x - }; +// This function estimates the structural weight of the machinery, main engine(s) and the remainder of the machinery weight. +// +// +// Inputs +// MCR is the total capacity of all generators in kW. +// hdb is the innerbootom height of the engine room +// Der is the height og the overhead of the engine room +// L is LWL or LBP +// B is molded beam +// T is molded draft +// +// Return +// It returns an object with the properties mass and the VCG. - }, +function parametricWeightMachinery(MCR, hdb, Der, B, T, L, test) { + // Calculates estimated machinery weight + let W = 0.72 * Math.pow(MCR, 0.78); + // Calculates LCG and VCG - mulComponents: function ( u, v ) { + // req1 and req2 are the Coast Guard requirements for the hdb + let req1 = (32 * B + 190 * Math.sqrt(T)) / 1000; + let req2 = (45.7 + 0.417 * L) / 100; + let reqmax = Math.max(req1, req2, hdb); - return { - x: u.x * v.x, - y: u.y * v.y, - z: u.z * v.z - }; + // VCGMach is the Vertical Center of Gravity of the machinery + let VCGMach = hdb + 0.35 * (Der - hdb); - }, + // LCGMach is the Longitudinal Center of Gravity of the machinery. Depends on designer - //Return the result of rotating the vector v by angles r={x,y,z} in radians. - //Intrinsic ZYX order (yaw,pitch,roll) is achieved by applying world axis rotations in XYZ order. - rotateTaitBryan: function ( v, r ) { + // Returns the object - let c, s; + return {mass: W, VCG: VCGMach}; +} - //Rotate around x axis - c = Math.cos( r.x ); - s = Math.sin( r.x ); - v = { - x: v.x, - y: v.y * c - v.z * s, - z: v.y * s + v.z * c - }; - //Then around y axis - c = Math.cos( r.y ); - s = Math.sin( r.y ); - v = { - x: v.z * s + v.x * c, - y: v.y, - z: v.z * c - v.x * s - }; +// This function estimates the remainder of the Light Ship Weight. It includes outfit: electrical plant, distributive auxiliary systems and hull engineering: bits, chocks, hatch covers... +// +// +// Inputs +// Co is the outfit weight coefficient. Parsons Chapter 11 Figure 11.17. Pag 24. +// LBP is the Lenght Between Perpendiculars. +// D is molded depth +// +// Return +// It returns an object with the properties mass and VCG. - //Then around z axis - c = Math.cos( r.z ); - s = Math.sin( r.z ); - v = { - x: v.x * c - v.y * s, - y: v.x * s + v.y * c, - z: v.z - }; +function parametricWeightOutfit(Co, LBP, D) { - return v; + // Calculates estimated weight + let W = Co * LBP; + // VCGOut is the Vertical Center of Gravity of the outfits + let VCGOut = 0; + if (LBP < 125) { + VCGOut = D + 1.25 + } + else if (LBP < 250) { + VCGOut = D + 1.25 + 0.01 * (LBP - 125) + } + else { + VCGOut = D + 2.5 } -}; - -class DerivedObject extends JSONSpecObject { - constructor( specification, baseObjects ) { + // LCGOut is the Longitudinal Center of Gravity of the Outfits. Depends on designer - super( specification, baseObjects ); + // Returns the object - } + return {mass: W, VCG: VCGOut}; +} +//@EliasHasle - setFromSpecification( spec ) { +//Very unoptimized for now. +function combineWeights(array) { + let M = array.reduce((sum, e) => sum + e.mass, 0); + let cgms = array.map(e => Vectors.scale(e.cg, e.mass)); + let CG = Vectors.scale(Vectors.sum(cgms), 1 / M); - this.id = spec.id; - this.group = spec.group || null; - this.affiliations = spec.affiliations; + return {mass: M, cg: CG}; +}//@EliasHasle - if ( typeof spec.baseObject === "string" ) { +/*Base class for objects that are constructed from +a literal object. - this.baseObject = this.baseObjects[ spec.baseObject ]; +Constructors can take more parameters than the specification, but the specification must be the first parameter. - } else { +setFromSpecification will typically be overridden by derived classes. Overriding implementations will typically do some sanity checking. - this.baseObject = new BaseObject( spec.baseObject ); +getSpecification will also typically be overridden. The default implementation here is just a sketch. Maybe not even correct for the simplest subclasses. - } - - this.referenceState = spec.referenceState; - //this.referenceStateVersion = 0; - this.style = spec.style || {}; - return this; - - } - - getSpecification() { - - let spec = { - id: this.id, - group: this.group, - affiliations: this.affiliations, - referenceState: this.referenceState, - style: this.style - }; - if ( this.baseObjects[ this.baseObject.id ] !== undefined ) { - - spec.baseObject = this.baseObject.id; - - } else { - - spec.baseObject = this.baseObject.getSpecification(); - - } - - return spec; +Maybe this can be improved by implementing fromJSON and to toJSON methods. +*/ +function JSONSpecObject(specification) { + if (specification === null) { + console.warn("JSONSpecObject: null specification provided. Defaulting to empty specification."); + specification = {}; } - - getWeight( state ) { - - let oState = state.getObjectState( this ); - - //Support disabled objects: - if ( oState.exists === false ) { - - return { mass: 0, cg: { x: 0, y: 0, z: 0 } }; - + else if (typeof specification === "object") {} + /*else if (typeof specification === "string") { + try { + specification = JSON.parse(specification); + } catch(e) { + console.error("JSONSpecObject: "+ e.message + "\n Defaulting to empty specification."); + specification = {}; } - - let p = { - x: oState.xCentre, - y: oState.yCentre, - z: oState.zBase - }; - - let w = this.baseObject.getWeight( oState.fullness ); - let m = w.mass; - let cg = Vectors.add( p, w.cg ); - - if ( isNaN( cg.x + cg.y + cg.z ) ) { - - console.error( "DerivedObject.getWeight: returning NaN values." ); - + }*/ + else { + if (typeof specification !== "undefined") { + console.error("JSONSpecObject: Invalid constructor parameter. Defaulting to empty specification."); } - - return { mass: m, cg: cg }; - + specification = {}; } - + this.setFromSpecification(specification); } - -//@MrEranwe -//@EliasHasle - -//Elias notes: -//LCB and LCG were obviously considered in another coordinate system than we are using. I have corrected this, assuming that the wrong coordinate system had the origin centered longitudinally. -//The hull mass is off by several orders of magnitude. Checking the paper, it seems likely that the "typical" K parameters are aimed at producing units of tonnes, not kg. -//It is not mathematically correct to strip down the structural weight calculation the way it is done here, because the exponentiation (E^1.36) cannot be simply decomposed as a sum of exponentiated terms (with the same exponent). -//Elias has only reviewed and modified the hull weight calculation. - -// This function estimates the structural weight of the hull. This includes the weight of the basic hull to its depth amidships. - -// It is based on Watson and Gilfillan modeling approach using a specific modification of the Lloyd’s Equipment Numeral E as the independent variable. -// -// -// Inputs -// K is the structural weight coefficient. Parsons, chapter 11, table 11.VII. -// L is LWL or LBP -// B is molded beam -// T is molded draft -// D is molded depth -// Superstructures and shiphouses are not being considered in the weight -// CB is the block coefficient -// LCB is the Longitudinal Center of Bouyancy -// -// Return -// It returns an object on the format {mass:1234, cg: {x:4,y:3,z:2}}, where the unit of mass is unclear, and x,y,z is in meters from aft,center,bottom, respectively. - -function parametricWeightHull( K, L, B, T, D, CB, Fn ) { - - // Calculates estimated structural weight - // E is the Lloyd’s Equipment Numeral - let E = L * ( B + T ) + 0.85 * L * ( D - T ); - // CbCorrected is the estimated corrected block coefficient - let CBCorrected = CB + ( 1 - CB ) * ( ( 0.8 * D - T ) / ( 3 * T ) ); - // W is the estimated structural weight - let W = K * Math.pow( E, 1.36 ) * ( 1 + 0.5 * ( CBCorrected - 0.7 ) ); - - // Calculates LCG and VCG - // VCGHull is the Vertical Center of Gravity of the hull - let VCGHull = 0; - if ( L < 120 ) { - - VCGHull = 0.01 * D * ( 46.6 + 0.135 * ( 0.81 - CB ) * Math.pow( L / D, 2 ) ) + 0.008 * D * ( L / D - 6.5 ); - - } else { - - VCGHull = 0.01 * D * ( 46.6 + 0.135 * ( 0.81 - CB ) * Math.pow( L / D, 2 ) ); - +JSONSpecObject.prototype = Object.create(Object.prototype); +Object.assign(JSONSpecObject.prototype, { + constructor: JSONSpecObject, + setFromSpecification: function(specification) { + //No sanity checking by default. + Object.assign(this, specification); + return this; + }, + getSpecification: function() { + let spec = {}; + for (k of Object.keys(this)) { + if (this.hasOwnProperty(k)) spec[k] = this[k]; + } + return spec; + }, + //toJSON is the standard way. Added here for testing. + toJSON: function() { + return this.getSpecification(); + }, + //fromJSON is added as an alternative and better name. + fromJSON: function(spec) { + return this.setFromSpecification(spec); } +});//@EliasHasle - // LCB is the longitudinal Center of Buoyancy converted from - // percentage plus forward of amidships to meters from aft - let LCB = Fn ? 0.5 * L + ( 9.7 - 45 * Fn ) * L / 100 : L * 0.516; - // LCGHull is the Longitudinal Center of Gravity of the hull in meters from aft - let LCGHull = LCB - 0.15 * L / 100; - - // Returns the object - - return { mass: W, cg: { x: LCGHull, y: 0, z: VCGHull } }; - -} - -//@EliasHasle - -//All inputs are numbers. The axes are given by a single coordinate. -function steiner( I, A, sourceAxis, targetAxis ) { - - return I + A * ( sourceAxis - targetAxis ) ** 2; +/* +Notes: +For calculated values, I envision a lazy calculation pattern, where all properties involved in calculations are only accessed by specialized getters and setters, and calculated properties have some kind of needsUpdate flag or version number (that is set by any setters that will directly or indirectly affect the property). When, and only when, running the getter for the given property, the flag/version is checked, and if false (same version as in cache) the getter just returns the stored value. If true, the getter starts the calculation of the value, invoking other getters. -} - -//Calculate area, center, Ix, Iy. -function trapezoidCalculation( xbase0, xbase1, xtop0, xtop1, ybase, ytop ) { - - let a = xbase1 - xbase0; - let b = xtop1 - xtop0; - let h = ytop - ybase; - if ( a < 0 || b < 0 || h < 0 ) { - - console.warn( "trapezoidCalculation: Unsupported input. Possibly not a valid trapezoid." ); - - } +Suggested calculations to do: +- Inertia matrix (will need more detailed properties of parts). +*/ - let A = 0.5 * ( a + b ) * h; - let yc = ( a == 0 && b == 0 ) ? ybase + 0.5 * h : ybase + h * ( 2 * a + b ) / ( 3 * ( a + b ) ); - let d = xbase0 + 0.5 * a; //shorthand - let xc = h === 0 ? 0.25 * ( xbase0 + xbase1 + xtop0 + xtop1 ) : d + ( xtop0 + 0.5 * b - d ) * ( yc - ybase ) / h; - let Ix = ( a == 0 && b == 0 ) ? 0 : h ** 3 * ( a ** 2 + 4 * a * b + b ** 2 ) / ( 36 * ( a + b ) ); +function Ship( specification ) { - //For Iy I must decompose (I think negative results will work fine): - let Art1 = 0.5 * ( xtop0 - xbase0 ) * h; - let xcrt1 = xbase0 + ( xtop0 - xbase0 ) / 3; - let Iyrt1 = ( xtop0 - xbase0 ) ** 3 * h / 36; - let Arec = ( xbase1 - xtop0 ) * h; - let xcrec = 0.5 * ( xtop0 + xbase1 ); - let Iyrec = ( xbase1 - xtop0 ) ** 3 * h / 12; - let Art2 = 0.5 * ( xbase1 - xtop1 ) * h; - let xcrt2 = ( xtop1 + ( xbase1 - xtop1 ) / 3 ); - let Iyrt2 = ( xbase1 - xtop1 ) ** 3 * h / 36; - - let Iy = steiner( Iyrt1, Art1, xcrt1, xc ) - + steiner( Iyrec, Arec, xcrec, xc ) - + steiner( Iyrt2, Art2, xcrt2, xc ); - - let maxX = Math.max.apply( null, [ xbase0, xbase1, xtop0, xtop1 ] ); - let minX = Math.min.apply( null, [ xbase0, xbase1, xtop0, xtop1 ] ); - let maxY = Math.max( ybase, ytop ); - let minY = Math.min( ybase, ytop ); - - return { A: A, xc: xc, yc: yc, Ix: Ix, Iy: Iy, maxX: maxX, minX: minX, maxY: maxY, minY: minY }; + JSONSpecObject.call( this, specification ); } -function combineAreas( array ) { - - let A = 0; - let xc = 0; - let yc = 0; - let maxX = 0, - minX = 0, - maxY = 0, - minY = 0; - let L = array.length; - let foundMinY = false; - let foundMaxY = false; - for ( let i = 0; i < L; i ++ ) { - - let e = array[ i ]; - A += e.A; - xc += e.xc * e.A; - yc += e.yc * e.A; - if ( ! isNaN( e.maxX ) && e.maxX > maxX ) maxX = e.maxX; - if ( ! isNaN( e.minX ) && e.minX < minX ) minX = e.minX; - if ( ! isNaN( e.maxY ) && e.maxY > maxY && ! foundMaxY && foundMinY ) { - - maxY = e.maxY; - if ( e.A === 0 ) { +Ship.prototype = Object.create( JSONSpecObject.prototype ); +Object.assign( Ship.prototype, { + constructor: Ship, + setFromSpecification: function ( specification ) { - foundMaxY = true; + this.attributes = specification.attributes || {}; + this.structure = new Structure( specification.structure/*,this*/ ); + //baseObjects and derivedObjects are arrays in the specification, but are objects (hashmaps) in the constructed ship object: + this.baseObjects = {}; + for ( let i = 0; i < specification.baseObjects.length; i ++ ) { - } + let os = specification.baseObjects[ i ]; + this.baseObjects[ os.id ] = new BaseObject( os ); } - if ( ! isNaN( e.minY ) && ! foundMinY ) { - - if ( e.A !== 0 ) { - - minY = e.minY; - foundMinY = true; + this.derivedObjects = {}; + for ( let i = 0; i < specification.derivedObjects.length; i ++ ) { - } + let os = specification.derivedObjects[ i ]; + this.derivedObjects[ os.id ] = new DerivedObject( os, this.baseObjects ); } - } + this.designState = new ShipState( specification.designState ); + return this; - if ( ! foundMaxY ) maxY = array[ L - 1 ].maxY; //if foundMaxY is false then the ship is a barge and a different logic must apply @ferrari212 + }, + getSpecification: function () { - let Ix = 0; - let Iy = 0; + let specification = {}; + specification.attributes = this.attributes; + specification.structure = this.structure.getSpecification(); - if ( A !== 0 ) { + specification.baseObjects = Object.values( this.baseObjects ).map( o => o.getSpecification() ); + specification.derivedObjects = Object.values( this.derivedObjects ).map( o => o.getSpecification() ); - xc /= A; - yc /= A; + specification.designState = this.designState.getSpecification(); - } else { + return specification; - //console.warn("Zero area combination."); - //console.trace(); - xc /= L; - yc /= L; + }, + //This should probably be separated in lightweight and deadweight + //Then this function should be replaced by a getDisplacement + getWeight: function ( shipState ) { - } + shipState = shipState || this.designState; - for ( let i = 0; i < array.length; i ++ ) { + let components = []; - let e = array[ i ]; - Ix += steiner( e.Ix, e.A, e.yc, yc ); - Iy += steiner( e.Iy, e.A, e.xc, xc ); + components.push( + this.structure.getWeight( this.designState ) + ); - } + //DEBUG + //console.log(components); - return { A: A, xc: xc, yc: yc, Ix: Ix, Iy: Iy, maxX: maxX, minX: minX, maxY: maxY, minY: minY }; + for ( let o of Object.values( this.derivedObjects ) ) { -} + components.push( o.getWeight( shipState ) ); -//x and y here refers to coordinates in the plane that is being calculated on. -function sectionCalculation( { xs, ymins, ymaxs } ) { + } - //console.group/*Collapsed*/("sectionCalculation"); - //console.info("Arguments (xs, ymins, ymaxs): ", arguments[0]); + var W = combineWeights( components ); + //console.info("Calculated weight object: ", W); + return W; - let calculations = []; - for ( let i = 0; i < xs.length - 1; i ++ ) { + }, + calculateDraft: function ( shipState, epsilon = 0.001, rho = 1025 ) { - let xbase = xs[ i ]; - let xtop = xs[ i + 1 ]; - let ybase0 = ymins[ i ] || 0; - let ybase1 = ymaxs[ i ] || 0; - let ytop0 = ymins[ i + 1 ] || 0; - let ytop1 = ymaxs[ i + 1 ] || 0; + let w = this.getWeight( shipState ); + let M = w.mass; + return this.structure.hull.calculateDraftAtMass( M, epsilon, rho ); - calculations.push( trapezoidCalculation( ybase0, ybase1, ytop0, ytop1, xbase, xtop ) ); + }, + //Separates between longitudinal and transverse GM + //To avoid confusion, no "default" GM or BM is specified in the output. + calculateStability: function ( shipState ) { - } + let w = this.getWeight( shipState ); + let KG = w.cg.z; + let LCG = w.cg.x; + let T = this.structure.hull.calculateDraftAtMass( w.mass ); + let { BMt, BMl, KB, LCB, LCF, LWL, BWL } = this.structure.hull.calculateAttributesAtDraft( T ); + let GMt = KB + BMt - KG; + let GMl = KB + BMl - KG; - let C = combineAreas( calculations ); //Might be zero areas! + // avaiable just for small angles < 3° + // this calculation can be incorporated to Vessesl.js with no problem + let trim = ( LCB - LCG ) / GMl; + let draftfp = 0; + let draftap = 0; + let trimd = 0; - let output = { A: C.A, maxX: C.maxY, minX: C.minY, maxY: C.maxX, minY: C.minX, xc: C.yc, yc: C.xc, Ix: C.Iy, Iy: C.Ix, Abb: ( C.maxY - C.minY ) * ( C.maxX - C.minX ) }; - //console.info("Output: ", output); - //console.groupEnd(); - return output; + if ( trim < Math.tan( 3 * Math.PI / 180 ) ) { -} + draftfp = T - ( LWL - LCF ) * trim; + draftap = T + ( LCF ) * trim; + trimd = draftfp - draftap; -//For wetted area. I think this is right, but it is not tested. -//The numerical integral will not scale well with larger geometries. -//Then the full analytical solution is needed. -function bilinearArea( x1, x2, y1, y2, z11, z12, z21, z22, segs = 5 ) { + } else { - let [ b00, b10, b01, b11 ] = bilinearCoeffs( x1, x2, y1, y2, z11, z12, z21, z22 ); - /* - z(x,y) = b00 + b10*x + b01*y + b11*xy - Partial derivative in x: b10 + b11*y - Partial derivative in y: b01 + b11*x - I think this will be right: - Tx(y) = (1, 0, b10+b11*y) - Ty(x) = (0, 1, b01+b11*x) - Then: - Tx X Ty = (-(b10+b11*y), -(b01+b11*x), 1) - |Tx X Ty| = sqrt((b10+b11*y)^2 + (b01+b11*x)^2 + 1) + draftfp = null; + draftap = null; + trimd = null; - Now, to get the area I need to integrate |Tx X Ty| over X,Y. + } - Wolfram Alpha gave me this for the inner integral using x (indefinite): - integral sqrt((b01 + b11 x)^2 + 1 + (b10+b11*y)^2) dx = ((b01 + b11*x) sqrt((b01 + b11*x)^2 + 1 + (b10+b11*y)^2) + (1 + (b10+b11*y)^2)*ln(sqrt((b01 + b11*x)^2 + 1 + (b10+b11*y)^2) + b01 + b11*x))/(2*b11) + constant - That means this for the definite integral: - ((b01 + b11*x2)*sqrt((b01 + b11*x2)^2 + 1 + (b10+b11*y)^2) + 1 + (b10+b11*y)^2*ln(sqrt((b01 + b11*x2)^2 + 1 + (b10+b11*y)^2) + b01 + b11*x2))/(2*b11) - ((b01 + b11*x1)*sqrt((b01 + b11*x1)^2 + 1 + (b10+b11*y)^2) + (1 + (b10+b11*y)^2)*ln(sqrt((b01 + b11*x1)^2 + 1 + (b10+b11*y)^2) + b01 + b11*x1))/(2*b11) - = - (b01 + b11*x2)*sqrt((b01 + b11*x2)^2 + 1 + (b10+b11*y)^2)/(2*b11) - +(1 + (b10+b11*y)^2)*ln(sqrt((b01 + b11*x2)^2 + 1 + (b10+b11*y)^2) + b01 + b11*x2)/(2*b11)) - - (b01 + b11*x1)*sqrt((b01 + b11*x1)^2 + 1 + (b10+b11*y)^2)/(2*b11) - - (1 + (b10+b11*y)^2)*ln(sqrt((b01 + b11*x1)^2 + 1 + (b10+b11*y)^2) + b01 + b11*x1)/(2*b11) - = - (b01 + b11*x2)*sqrt((b01 + b11*x2)^2 + 1 + (b10+b11*y)^2)/(2*b11) - - (b01 + b11*x1)*sqrt((b01 + b11*x1)^2 + 1 + (b10+b11*y)^2)/(2*b11) - +(1 + (b10+b11*y)^2)*ln(sqrt((b01 + b11*x2)^2 + 1 + (b10+b11*y)^2) + b01 + b11*x2)/(2*b11)) - - (1 + (b10+b11*y)^2)*ln(sqrt((b01 + b11*x1)^2 + 1 + (b10+b11*y)^2) + b01 + b11*x1)/(2*b11) - - The two first integrals are similar, and the two last are also similar. With A=+-(b01 + b11*xi)/(2*b11), B=(b01 + b11*xi)^2+1, C=b10 and D=b11 (where xi represents either x1 or x2, and +- represents + for x2 and - for x1), I can calculate the integral of sqrt(B+(C+D*y)^2) and multiply by A. That integral is on the same form as the first one. - - The two last integrals can be represented by setting A=+-1/(2*b11), B=(b01 + b11*xi)^2+1, C=b01+b11*xi, D=b10, E=b11, and calculating the integral of (1+(D+E*y)^2)*ln(sqrt(B+(D+E*y)^2)+C), and multiplying by A. - Here is the integration result from Wolfram Alpha: - integral(1 + (D + E y)^2) log(sqrt(B + (D + E y)^2) + C) dy = (-(6 (B^2 - 2 B C^2 - 3 B + C^4 + 3 C^2) tan^(-1)((D + E y)/sqrt(B - C^2)))/sqrt(B - C^2) + (6 (B^2 - 2 B C^2 - 3 B + C^4 + 3 C^2) tan^(-1)((C (D + E y))/(sqrt(B - C^2) sqrt(B + (D + E y)^2))))/sqrt(B - C^2) + 6 (B - C^2 - 3) (D + E y) + 3 C (-3 B + 2 C^2 + 6) log(sqrt(B + (D + E y)^2) + D + E y) + 3 C (D + E y) sqrt(B + (D + E y)^2) + 6 ((D + E y)^2 + 3) (D + E y) log(sqrt(B + (D + E y)^2) + C) - 2 (D + E y)^3)/(18 E) + constant - - I am glad I did not try to do this by hand. But combining these formulae, we can get an exact integral of the area of a bilinear patch. Later. Bilinear patches are not an exact representation anyway. We may opt for something else. - */ - - //Simple numerical calculation of double integral: - let A = 0; - let X = x2 - x1, Y = y2 - y1; - let N = segs, M = segs; - for ( let i = 0; i < N; i ++ ) { - - let x = x1 + ( ( i + 0.5 ) / N ) * X; - for ( let j = 0; j < M; j ++ ) { - - let y = y1 + ( ( j + 0.5 ) / M ) * Y; - A += Math.sqrt( ( b10 + b11 * y ) ** 2 + ( b01 + b11 * x ) ** 2 + 1 ); - - } - - } - - A *= X * Y / ( N * M ); //dx dy - - return A; - -} - -//@EliasHasle - -//I have been doing some tests here of a simplified calculation. -//The results so far indicate that, for the prism hull, the results are almost identical, except that with the simple calculation the center of volume is almost right (but wrong enough to disqualify such a simple calculation). -/*Note that the coordinate system used here has xy as a grid, with z as heights on the grid, but in the intended application, which is calculations on transverse hull offsets, this z corresponds to the vessel y axis, and y corresponds to the vessel z axis. In any application of this function, the conversion between coordinate systems must be taken care of appropriately.*/ -// xy -function patchColumnCalculation( x1, x2, y1, y2, z00, z01, z10, z11 ) { - - //VOLUME: - //Analysis based on a bilinear patch: - // /* - // From here I call mux for x, and muy for y. - // Integral over unit square: - // INT[x from 0 to 1, INT[y from 0 to 1, (a00 + a10*x + a01*y + a11*x*y) dy] dx] - // = INT[x from 0 to 1, (a00+a10*x+0.5*a01+0.5*a11*x) dx] - // = a00 + 0.5*a10 + 0.5*a01 + 0.25*a11 - // Note that by expanding a00,a10,a01,a11, it is demonstrated that this (rather unsurprisingly) is exactly equivalent to taking the average z offset of the control points. - // */ - let X = x2 - x1; - let Y = y2 - y1; - let Ab = X * Y; //area of base of patch column - //let zAvg = (a00 + 0.5*a10 + 0.5*a01 + 0.25*a11); - let zAvg = 0.25 * ( z00 + z01 + z10 + z11 ); //equivalent - let V = Math.abs( Ab * zAvg ); //works - - //CENTER OF VOLUME - let zc = 0.5 * zAvg; - - //Very approximate center of volume - //(does not account for different signs on z values, - //but that should be OK for hull offsets) - //let xc = (x1*(z00+z01)+x2*(z10+z11))/((z00+z01+z10+z11) || 1); - //let yc = (y1*(z00+z10)+y2*(z01+z11))/((z00+z01+z10+z11) || 1); - - // /* - // To find xc properly, I need to integrate x*z over the unit square, divide by zAvg(?) and scale and translate to ship coordinates afterwards: - // INT[x from 0 to 1, INT[y from 0 to 1, x*(a00 + a10*x + a01*y + a11*x*y) dy] dx] = - // INT[x from 0 to 1, INT[y from 0 to 1, (a00*x + a10*x^2 + a01*xy + a11*x^2*y) dy] dx] = - // INT[x from 0 to 1, (a00*x + a10*x^2 + 0.5*a01*x + 0.5*a11*x^2) dx] - // = (0.5*a00 + a10/3 + 0.25*a01 + a11/6) - //Trying to expand the coeffs to original z offsets: - // = (0.5*z00 + (z10-z00)/3 + 0.25*(z01-z00) + (z00+z00-z01-z10)/6) - // = ((1/12)*z00 + (1/6)*z10 + (1/12)*z01 + (1/6)*z00) - //Divide by zAvg to get muxc, then scale and translate to xc. - let xc = x1 + X * ( ( ( 1 / 12 ) * z00 + ( 1 / 6 ) * z10 + ( 1 / 12 ) * z01 + ( 1 / 6 ) * z11 ) / ( zAvg || 1 ) ); - //console.log("x1=%.2f, X=%.2f, muxc = %.2f", x1, X, (((1/12)*z00 + (1/6)*z10 + (1/12)*z01 + (1/6)*z11) / (zAvg || 1))); - //Similar for yc (modified symmetrically) - let yc = y1 + Y * ( ( ( 1 / 12 ) * z00 + ( 1 / 12 ) * z10 + ( 1 / 6 ) * z01 + ( 1 / 6 ) * z11 ) / ( zAvg || 1 ) ); - - //console.log("Patch column Cv = (%.2f, %.2f, %.2f)", xc,yc,zc); - - //AREA - //These two methods give very similar results, within about 1% difference for the fishing boat hull (used in PX121.json). - //Simple triangle average approximation for area (works) - /*let As = elementArea( - {x: x1, y: y1, z: z00}, - {x: x1, y: y2, z: z01}, - {x: x2, y: y1, z: z10}, - {x: x2, y: y2, z: z11});*/ - //Bilinear area calculation. Works too, but is currently numerical, and quite complex (which means it is bug-prone and hard to maintain). But it is more exact, even with just a few segments for numerical integration (the last, optional, parameter) - let As = Math.abs( bilinearArea( x1, x2, y1, y2, z00, z01, z10, z11 ) ); - - return { Ab: Ab, As: As, V: V, Cv: { x: xc, y: yc, z: zc } }; - -} - -//Input: array of objects with calculation results for elements. -//Output: the combined results. -function combineVolumes( array ) { - - let V = 0; - let As = 0; - let Cv = { x: 0, y: 0, z: 0 }; - let L = array.length; - //if (L===0) return {V,As,Cv}; - for ( let i = 0; i < L; i ++ ) { - - let e = array[ i ]; - V += e.V; - As += e.As; //typically wetted area - //console.log(e.Cv); - Cv = Vectors.add( Cv, Vectors.scale( e.Cv, e.V ) ); - - } - - Cv = Vectors.scale( Cv, 1 / ( V || L || 1 ) ); - - //console.info("combineVolumes: Combined Cv is (" + Cv.x + ", " + Cv.y + ", " + Cv.z + ")."); - - return { V, As, Cv };//{V: V, As: As, Cv: Cv}; - -} - -class Hull extends JSONSpecObject { - - constructor( spec ) { - - super( spec ); - - } - - setFromSpecification( spec ) { - - this.halfBreadths = spec.halfBreadths; - //this.buttockHeights = spec.buttockHeights; - this.attributes = spec.attributes; //this could/should include LOA, BOA, Depth - this.levelsNeedUpdate = true; - this.style = spec.style || {}; - return this; - - } - - getWeight( designState ) { - - let ha = this.attributes; - let B = ha.BOA; - let D = ha.Depth; - let cp = designState.calculationParameters; - let K = cp.K; - let L = cp.LWL_design; - let T = cp.Draft_design; - let Cb = cp.Cb_design; - let vsm = 0.514444 * cp.speed; // Convert the design speed from knots to m/s - let Fn = vsm / Math.pow( 9.81 * L, 0.5 ); // Calculates Froude number - - //This is not a good way to estimate the hull weight. - let parsons = parametricWeightHull( K, L, B, T, D, Cb, Fn ); - parsons.mass *= 1000; //ad hoc conversion to kg, because the example K value is aimed at ending with tonnes. - - let output = parsons; - //console.info("Hull weight:", output); - return output; - - } - - getStation( x ) { - - let ha = this.attributes; - let xr = x / ha.LOA; - let sts = this.halfBreadths.stations; - let wls = this.halfBreadths.waterlines; - let tab = this.halfBreadths.table; - - let { index: a, mu: mu } = bisectionSearch( sts, xr ); - - let st; - if ( a < 0 || a >= sts.length ) st = new Array( wls.length ).fill( null ); - else if ( a + 1 === sts.length ) st = tab.map( row => row[ sts.length - 1 ] ); - else { - - st = []; - for ( let j = 0; j < wls.length; j ++ ) { - - let after = tab[ j ][ a ]; - let forward = tab[ j ][ a + 1 ]; - if ( ( after === null || isNaN( after ) ) && ( forward === null || isNaN( forward ) ) ) { - - st.push( null ); - - } else { - - //Simply correcting by "|| 0" is not consistent with what is done in getWaterline. It may be better to correct upper nulls by nearest neighbor below. - st.push( lerp( after || 0, forward || 0, mu ) ); - - } - - } - - } - - for ( let j = 0; j < this.halfBreadths.waterlines.length; j ++ ) { - - st[ j ] *= 0.5 * ha.BOA; - if ( isNaN( st[ j ] ) || st[ j ] === null ) st[ j ] = null; - - } - - return st; - - } - - getWaterline( z ) { - - let ha = this.attributes; - let zr = z / ha.Depth; //using zr requires fewer operations and less memory than a scaled copy of wls. - let wls = this.halfBreadths.waterlines;//.map(wl=>wl*ha.Depth); - let sts = this.halfBreadths.stations; - let tab = this.halfBreadths.table; - - if ( zr < wls[ 0 ] ) { - - //console.warn("getWaterLine: z below lowest defined waterline. Defaulting to all zero offsets."); - return new Array( sts.length ).fill( 0 ); - - } else { - - let a, mu; - if ( zr > wls[ wls.length - 1 ] ) { - - //console.warn("getWaterLine: z above highest defined waterline. Proceeding with highest data entries."); - a = wls.length - 2; //if this level is defined... - mu = 1; - //wl = tab[a].slice(); - - } else { - - ( { index: a, mu: mu } = bisectionSearch( wls, zr ) ); - if ( a === wls.length - 1 ) { - - a = wls.length - 2; - mu = 1; - - } - - } - - //Try to do linear interpolation between closest data waterlines, but handle null values well: - let wl = new Array( sts.length ); - for ( let j = 0; j < wl.length; j ++ ) { - - let lower, upper; - let b = a; - //Find lower value for interpolation - if ( tab[ b ][ j ] !== null && ! isNaN( tab[ b ][ j ] ) ) { - - lower = tab[ b ][ j ]; - - } else { - - b = a + 1; - while ( b < wls.length && ( isNaN( tab[ b ][ j ] ) || tab[ b ][ j ] === null ) ) { - - b ++; - - } - - if ( b !== wls.length ) { - - //Inner NaN - lower = 0; - - } else { - - //Upper NaN, search below: - b = a - 1; - while ( b >= 0 && ( isNaN( tab[ b ][ j ] ) || tab[ b ][ j ] === null ) ) { - - b --; - - } - - if ( b === - 1 ) { - - //No number found: - lower = 0; - upper = 0; - - } else { - - lower = tab[ b ][ j ]; - upper = lower; - - } - - } - - } - - //Find upper value for interpolation - let c = a + 1; - if ( upper !== undefined ) ; else if ( tab[ c ][ j ] !== null && ! isNaN( tab[ c ][ j ] ) ) { - - upper = tab[ c ][ j ]; - - } else { - - //The cell value is NaN. - //Upper is not defined. - //That means either tab[a][j] is a number - //or tab[a][j] is an inner NaN and - //there exists at least one number above it. - //In both cases I have to check above a+1. - c = a + 2; - while ( c < wls.length && ( isNaN( tab[ c ][ j ] ) || tab[ c ][ j ] === null ) ) { - - c ++; - - } - - if ( c === wls.length ) upper = lower; - else { - - upper = tab[ c ][ j ]; - - } - - } - - //Linear interpolation - wl[ j ] = lerp( lower, upper, mu ); - //Scale numerical values - if ( wl[ j ] !== null && ! isNaN( wl[ j ] ) ) wl[ j ] *= 0.5 * ha.BOA; - - } - - return wl; - - } - - } - - //typically deck bounds - waterlineCalculation( z, bounds ) { - - let { minX, maxX, minY, maxY } = bounds || {}; - - //console.group/*Collapsed*/("waterlineCalculation."); - //console.info("Arguments: z=", z, " Boundaries: ", arguments[1]); - - let wl = this.getWaterline( z ); - //console.info("wl: ", wl); //DEBUG - - let LOA = this.attributes.LOA; - - let sts = this.halfBreadths.stations.slice(); - for ( let i = 0; i < sts.length; i ++ ) { - - sts[ i ] *= LOA; - - } - - let hasMinX = ( minX !== undefined ) && minX !== sts[ 0 ]; - let hasMaxX = ( maxX !== undefined ) && maxX !== sts[ sts.length - 1 ]; - if ( hasMinX || hasMaxX ) { - - let first = 0; - let wlpre; - if ( hasMinX ) { - - let muf; - ( { index: first, mu: muf } = bisectionSearch( sts, minX ) ); - let lower = wl[ first ]; - let upper = wl[ first + 1 ]; - if ( ( lower === null || isNaN( lower ) ) && ( upper === null || isNaN( upper ) ) ) { - - wlpre = null; - - } else { - - wlpre = lerp( lower || 0, upper || 0, muf ); - - } - - } - - let last = sts.length - 1; - let wlsuff; - if ( hasMaxX ) { - - let mul; - ( { index: last, mu: mul } = bisectionSearch( sts, maxX ) ); - let lower = wl[ last ]; - let upper = wl[ last + 1 ]; - if ( ( lower === null || isNaN( lower ) ) && ( upper === null || isNaN( upper ) ) ) { - - wlsuff = null; - - } else { - - wlsuff = lerp( lower || 0, upper || 0, mul ); - - } - - } - - //Add virtual entries according to specified boundaries: - sts = sts.slice( first + 1, last + 1 ); - wl = wl.slice( first + 1, last + 1 ); - if ( hasMinX ) { - - sts.unshift( minX ); - wl.unshift( wlpre ); - - } - - if ( hasMaxX ) { - - sts.push( maxX ); - wl.push( wlsuff ); - - } - - } - - //This does not yet account properly for undefined minY, maxY. - let port = [], star = []; - for ( let i = 0; i < wl.length; i ++ ) { - - if ( wl[ i ] === null || isNaN( wl[ i ] ) ) { - - star[ i ] = minY || null; - port[ i ] = maxY || null; - - } else { - - star[ i ] = Math.max( - wl[ i ], minY || - wl[ i ] ); - port[ i ] = Math.min( wl[ i ], maxY || wl[ i ] ); - - } - - } - - //DEBUG - //console.info("Arguments to sectionCalculation:", sts, star, port); - - //sectionCalculation can potentially be served some nulls. - let sc = sectionCalculation( { xs: sts, ymins: star, ymaxs: port } ); - let LWL = sc.maxX - sc.minX; - let BWL = sc.maxY - sc.minY; - let Cwp = sc.A / ( LWL * BWL ); - let APP = this.attributes.APP || sc.minX; - let FPP = this.attributes.FPP || sc.maxX; - let LBP = FPP - APP; - - let output = { - z: z, - xc: sc.xc, - yc: sc.yc, - Awp: sc.A, - Ix: sc.Ix, - Iy: sc.Iy, - maxX: sc.maxX, - minX: sc.minX, - maxY: sc.maxY, - minY: sc.minY, - Cwp: Cwp, - LWL: LWL, - LBP: LBP, - BWL: BWL - }; - //console.info("Output from waterlineCalculation: ", output); - //console.groupEnd(); - return output; - - } - - stationCalculation( x, maxZ ) { - - let wls = this.halfBreadths.waterlines.map( wl => this.attributes.Depth * wl ); - let port = this.getStation( x ); - if ( maxZ !== null && ! isNaN( maxZ ) ) { - - let { index, mu } = bisectionSearch( wls, maxZ ); - if ( index < wls.length - 1 ) { - - wls[ index + 1 ] = lerp( wls[ index ], wls[ index + 1 ], mu ); - port[ index + 1 ] = lerp( port[ index ], port[ index + 1 ], mu ); - wls = wls.slice( 0, index + 2 ); - port = port.slice( 0, index + 2 ); - - } - - } - - let star = port.map( hb => - hb ); - - let sc = sectionCalculation( { xs: wls, ymins: star, ymaxs: port } ); - return { - x: x, //or xc? or cg.. Hm. - yc: sc.yc, - zc: sc.xc, - A: sc.A, - Iz: sc.Ix, - Iy: sc.Iy, - maxZ: sc.maxX, - minZ: sc.minX, - maxY: sc.maxY, - minY: sc.minY - }; - - } - - calculateAttributesAtDraft( T ) { - - function levelCalculation( hull, - z, - prev = { - z: 0, - Vs: 0, - Vbb: 0, - As: 0, - minX: 0, - maxX: 0, - minY: 0, - maxY: 0, - prMinY: 0, - prMaxY: 0, - Ap: 0, - Cv: { x: 0, y: 0, z: 0 } - } ) { - - let wlc = hull.waterlineCalculation( z, {} ); - let lev = {}; - Object.assign( lev, wlc ); - //Projected area calculation (approximate): - lev.prMinY = wlc.minY; - lev.prMaxY = wlc.maxY; - //DEBUG: - //console.info("prev.Ap = ", prev.Ap); - //console.info("Parameters to trapezoidCalculation: (%.2f, %.2f, %.2f, %.2f, %.2f, %.2f)", prev.prMinY, prev.prMaxY, lev.prMinY, lev.prMaxY, prev.z, z); - let AT = trapezoidCalculation( prev.prMinY, prev.prMaxY, lev.prMinY, lev.prMaxY, prev.z, z )[ "A" ]; - //console.log("Calculated area of trapezoid: ", AT); - lev.Ap = prev.Ap + AT; - //lev.Ap = prev.Ap - // + trapezoidCalculation(prev.prMinY, prev.prMaxY, lev.prMinY, lev.prMaxY, prev.z, z)["A"]; - //DEBUG END - - - //level bounds are for the bounding box of the submerged part of the hull - if ( wlc.minX !== null && ! isNaN( wlc.minX ) && wlc.minX <= prev.minX ) - lev.minX = wlc.minX; - else - lev.minX = prev.minX; - if ( wlc.maxX !== null && ! isNaN( wlc.maxX ) && wlc.maxX >= prev.maxX ) - lev.maxX = wlc.maxX; - else - lev.maxX = prev.maxX; - if ( wlc.minY !== null && ! isNaN( wlc.minY ) && wlc.minY <= prev.minY ) - lev.minY = wlc.minY; - else - lev.minY = prev.minY; - if ( wlc.maxY !== null && ! isNaN( wlc.maxY ) && wlc.maxY >= prev.maxY ) - lev.maxY = wlc.maxY; - else - lev.maxY = prev.maxY; - - lev.Vbb = ( lev.maxX - lev.minX ) * ( lev.maxY - lev.minY ) * z; - - //Keep level maxX and minX for finding end cap areas: - lev.maxXwp = wlc.maxX; - lev.minXwp = wlc.minX; - - //Find bilinear patches in the slice, and combine them. - //Many possibilities for getting the coordinate systems wrong. - let calculations = []; - let sts = hull.halfBreadths.stations.map( st => st * hull.attributes.LOA ); - let wl = hull.getWaterline( z ); - let prwl = hull.getWaterline( prev.z ); - for ( let j = 0; j < sts.length - 1; j ++ ) { - - let port = - patchColumnCalculation( sts[ j ], sts[ j + 1 ], prev.z, z, - prwl[ j ], - wl[ j ], - prwl[ j + 1 ], - wl[ j + 1 ] ); - calculations.push( port ); - let star = - patchColumnCalculation( sts[ j ], sts[ j + 1 ], prev.z, z, prwl[ j ], wl[ j ], prwl[ j + 1 ], wl[ j + 1 ] ); - calculations.push( star ); - - } - - //console.log(calculations); //DEBUG - let C = combineVolumes( calculations ); - //Cv of slice. Note that switching of yz must - //be done before combining with previous level - let Cv = { x: C.Cv.x, y: C.Cv.z, z: C.Cv.y }; - - lev.Vs = prev.Vs + C.V; //hull volume below z - lev.As = prev.As + C.As; //outside surface below z - - //End caps: - if ( lev.minXwp <= sts[ 0 ] ) - lev.As += hull.stationCalculation( lev.minXwp, z )[ "A" ]; - if ( lev.maxXwp >= sts[ sts.length - 1 ] ) - lev.As += hull.stationCalculation( lev.maxXwp, z )[ "A" ]; - - //center of volume below z (some potential for accumulated rounding error when calculating an accumulated average like this): - lev.Cv = Vectors.scale( Vectors.add( - Vectors.scale( prev.Cv, prev.Vs ), - Vectors.scale( Cv, C.V ) - ), 1 / ( lev.Vs || 2 ) ); - - lev.Cb = lev.Vs / lev.Vbb; - lev.Cp = lev.Vs / ( lev.Ap * ( lev.maxX - lev.minX ) ); - - return lev; - - } - - if ( T === null || isNaN( T ) ) { - - console.error( "Hull.prototype.calculateAttributesAtDraft(T): No draft specified. Returning undefined." ); - return; - - } else if ( T < 0 || T > this.attributes.Depth ) { - - console.error( "Hull.prototype.calculateAttributesAtDraft(T): Draft parameter " + T + "outside valid range of [0,Depth]. Returning undefined." ); - - } - - let wls = this.halfBreadths.waterlines.map( wl => this.attributes.Depth * wl ); - - // new ES6 - //This is the part that can be reused as long as the geometry remains unchanged: - if ( this.levelsNeedUpdate ) { - - this.levels = []; - for ( let i = 0; i < wls.length; i ++ ) { - - let z = wls[ i ]; - let lev = levelCalculation( this, z, this.levels[ i - 1 ] ); - //Bottom cap, only on the lowest level: - if ( i === 0 ) { - - lev.As += lev.Awp; - - } - - this.levels.push( lev ); - - } - - this.levelsNeedUpdate = false; - - } - - //Find highest data waterline below or at water level: - let { index, mu } = bisectionSearch( wls, T ); - - //console.info("Highest data waterline below or at water level: " + index); - //console.log(this.levels); - let lc; - if ( mu === 0 ) lc = this.levels[ index ]; - else lc = levelCalculation( this, T, this.levels[ index ] ); - - //Filter and rename for output - return { - xcwp: lc.xc, //water plane values - LCF: lc.xc, - ycwp: lc.yc, - TCF: lc.yc, - Awp: lc.Awp, - Ixwp: lc.Ix, - BMt: lc.Ix / lc.Vs, - Iywp: lc.Iy, - BMl: lc.Iy / lc.Vs, - maxXs: lc.maxX, //boundaries of the submerged part of the hull - minXs: lc.minX, - maxYs: lc.maxY, - minYs: lc.minY, - Cwp: lc.Cwp, - LWL: lc.LWL, - LBP: lc.LBP, - BWL: lc.BWL, - Ap: lc.Ap, //projected area in length direction - Cp: lc.Cp, //prismatic coefficient - //Vbb: lc.Vbb, - Vs: lc.Vs, //volume of submerged part of the hull - Cb: lc.Cb, - Cm: lc.Cb / lc.Cp, - As: lc.As, //wetted area - Cv: lc.Cv, //center of buoyancy - LCB: lc.Cv.x, - TCB: lc.Cv.y, - KB: lc.Cv.z + //change the trim for angles + trim = Math.atan( trim ) * 180 / Math.PI; + console.log( trimd ); + let heel = w.cg.y / GMt; + //change the hell for meters + heel *= BWL; - }; + return { w, T, GMt, GMl, KB, BMt, BMl, KG, trim, draftfp, draftap, trimd, heel }; - } - // }() + }, + getFuelMass: function ( shipState ) { - //M is the mass (in kg) of the ship - calculateDraftAtMass( M, epsilon = 0.001, rho = 1025 ) { + shipState = shipState || this.designState; - let VT = M / rho; //Target submerged volume (1025=rho_seawater) - //Interpolation: - let a = 0; - let b = this.attributes.Depth; //depth is not draft ¿? - let t = 0.5 * b; - //Souce: https://en.wikipedia.org/wiki/Secant_method - // Secant Method to Find out where is the zero point - // Used to find out the Draft but can be generalized - let V1 = 0 - VT; - let V2 = VT; //Just inserting V2 an ordinary value to not have to calculate it twice - while ( Math.abs( t - a ) > epsilon ) { + let fuelMass = {}; + fuelMass.totalMass = 0; + fuelMass.tankMass = {}; + fuelMass.tankStates = {}; + for ( let o of Object.values( this.derivedObjects ) ) { - //This following condition force just receive draft from [0;Depth] - if ( t > b ) { + if ( o.affiliations.group === "fuel tanks" ) { - t = b; + fuelMass.tankStates[ o.id ] = shipState.getObjectState( o ); + fuelMass.tankMass[ o.id ] = o.baseObject.weightInformation.contentDensity * o.baseObject.weightInformation.volumeCapacity * fuelMass.tankStates[ o.id ].fullness; + fuelMass.totalMass += fuelMass.tankMass[ o.id ]; } - V2 = this.calculateAttributesAtDraft( t )[ "Vs" ] - VT; - // debugger - let dx = ( V2 - V1 ) / ( t - a ); - if ( dx > 0.1 || dx < - 0.1 ) { - - a = t; - V1 = V2; - t = t - V2 / dx; - //In case the derived of function is close to 0 we can follow the Bisection method - //Source: https://en.wikipedia.org/wiki/Bisection_method - - } else { + } - let ts = 0.5 * ( a + t ); //intermediate point - let Vs = this.calculateAttributesAtDraft( ts )[ "Vs" ] - VT; //this values must be calculated twice, see better example - if ( Vs > 0 ) { + return fuelMass; - t = ts; - V2 = Vs; + }, + subtractFuelMass: function ( mass, shipState ) { - } else { + shipState = shipState || this.designState; - a = ts; - V1 = Vs; + var fuelMass = this.getFuelMass( shipState ); + var totalFuel = fuelMass.totalMass; + var tankEntr = Object.entries( fuelMass.tankMass ); - } + var fuelCap = 0; + for ( var tk = 0; tk < tankEntr.length; tk ++ ) { - } + var tkId = tankEntr[ tk ][ 0 ]; + fuelCap += this.derivedObjects[ tkId ].baseObject.weightInformation.volumeCapacity * this.derivedObjects[ tkId ].baseObject.weightInformation.contentDensity; } - return t; + // check if tanks have necessary fuel + if ( mass <= totalFuel ) { // if yes, subtract mass in the same proportion - } + for ( var tk = 0; tk < tankEntr.length; tk ++ ) { - getSpecification() { + var tkId = tankEntr[ tk ][ 0 ]; + shipState.objectCache[ tkId ].state.fullness -= mass / fuelCap; - return { - halfBreadths: this.halfBreadths, - //buttockHeights: this.buttockHeights - attributes: this.attributes, - style: this.style - }; + } - } + } else { // if not, make tanks empty + mass -= totalFuel; + for ( var tk = 0; tk < tankEntr.length; tk ++ ) { + var tkId = tankEntr[ tk ][ 0 ]; + shipState.objectCache[ tkId ].state.fullness = 0; -} + } -//@EliasHasle + console.error( "Vessel ran out of fuel before " + mass.toFixed( 2 ) + " tons were subtracted." ); + } -//Very unoptimized for now. -function combineWeights( array ) { + // update related states. In the future, make this consistent with improved caching system + for ( var prop in shipState.objectCache ) { - let M = array.reduce( ( sum, e ) => sum + e.mass, 0 ); - let cgms = array.map( e => Vectors.scale( e.cg, e.mass ) ); - let CG = Vectors.scale( Vectors.sum( cgms ), 1 / M ); + shipState.objectCache[ prop ].thisStateVer ++; - return { mass: M, cg: CG }; + } -} + shipState.version ++; + } +} ); //@EliasHasle -class Structure extends JSONSpecObject { - - constructor( spec ) { +function Structure( spec/*, ship*/ ) { - //this.ship = ship; - super( spec ); + //this.ship = ship; + JSONSpecObject.call( this, spec ); - } +} - setFromSpecification( spec ) { +Structure.prototype = Object.create( JSONSpecObject.prototype ); +Object.assign( Structure.prototype, { + setFromSpecification: function ( spec ) { this.hull = new Hull( spec.hull/*, this.ship*/ ); this.decks = spec.decks;/*{}; @@ -1589,9 +1043,8 @@ class Structure extends JSONSpecObject { return this; - } - - getSpecification() { + }, + getSpecification: function () { let spec = { hull: this.hull.getSpecification(), @@ -1615,9 +1068,9 @@ class Structure extends JSONSpecObject { return spec; - } - - getWeight( designState ) { + }, + //This is all dummy calculations + getWeight: function ( designState ) { let components = []; //Hull @@ -1667,172 +1120,237 @@ class Structure extends JSONSpecObject { return output; } +} ); +//@EliasHasle + +/*When having a class for this, the specification can possibly be in one of several formats, and the handling will be contained in this class. + +I have tried to remove the dependency on the ship object here. This is in order to be able to optimize updates. + +This class needs more comments, for shure. + +And the geometric calculations are faulty. +*/ + +function Hull( spec ) { + + JSONSpecObject.call( this, spec ); } -//@EliasHasle +Hull.prototype = Object.create( JSONSpecObject.prototype ); +Object.assign( Hull.prototype, { + constructor: Hull, + setFromSpecification: function ( spec ) { + this.halfBreadths = spec.halfBreadths; + //this.buttockHeights = spec.buttockHeights; + this.attributes = spec.attributes; //this could/should include LOA, BOA, Depth + this.levelsNeedUpdate = true; + this.style = spec.style || {}; + return this; -class ShipState extends JSONSpecObject { + }, + getSpecification: function () { - constructor( specification ) { + return { + halfBreadths: this.halfBreadths, + //buttockHeights: this.buttockHeights + attributes: this.attributes, + style: this.style + }; - super( specification ); + }, + //to facilitate economical caching, it may be best to have a few numerical parameters to this function instead of letting it depend on the whole designState. Or maybe the designState is static enough. + getWeight: function ( designState ) { - } + let ha = this.attributes; + let B = ha.BOA; + let D = ha.Depth; + let cp = designState.calculationParameters; + let K = cp.K; + let L = cp.LWL_design; + let T = cp.Draft_design; + let Cb = cp.Cb_design; + let vsm = 0.514444 * cp.speed; // Convert the design speed from knots to m/s + let Fn = vsm / Math.pow( 9.81 * L, 0.5 ); // Calculates Froude number - getSpecification() { + //This is not a good way to estimate the hull weight. + let parsons = parametricWeightHull( K, L, B, T, D, Cb, Fn ); + parsons.mass *= 1000; //ad hoc conversion to kg, because the example K value is aimed at ending with tonnes. - if ( this.cachedVersion !== this.version ) { + let output = parsons; + //console.info("Hull weight:", output); + return output; - var spec = { - calculationParameters: this.calculationParameters, - objectOverrides: this.objectOverrides//{} - }; + }, + /* + Testing new version without nanCorrectionMode parameter, that defaults to setting lower NaNs to 0 and extrapolating highest data entry for upper NaNs (if existant, else set to 0). Inner NaNs will also be set to zero. - //Sketchy, but versatile: - spec = JSON.parse( JSON.stringify( spec ) ); + Input: + z: level from bottom of ship (absolute value in meters) - this.specCache = spec; - this.cachedVersion = this.version; + Output: + Array representing waterline offsets for a given height from the keel (typically a draft). + */ + getWaterline: function ( z ) { - } + let ha = this.attributes; + let zr = z / ha.Depth; //using zr requires fewer operations and less memory than a scaled copy of wls. + let wls = this.halfBreadths.waterlines;//.map(wl=>wl*ha.Depth); + let sts = this.halfBreadths.stations; + let tab = this.halfBreadths.table; - return this.specCache; + if ( zr < wls[ 0 ] ) { - } + //console.warn("getWaterLine: z below lowest defined waterline. Defaulting to all zero offsets."); + return new Array( sts.length ).fill( 0 ); - clone() { + } else { - return new ShipState( this.getSpecification() ); + let a, mu; + if ( zr > wls[ wls.length - 1 ] ) { - } + //console.warn("getWaterLine: z above highest defined waterline. Proceeding with highest data entries."); + a = wls.length - 2; //if this level is defined... + mu = 1; + //wl = tab[a].slice(); - getObjectState( o ) { + } else { - if ( this.objectCache[ o.id ] !== undefined ) { + ( { index: a, mu: mu } = bisectionSearch( wls, zr ) ); + if ( a === wls.length - 1 ) { - let c = this.objectCache[ o.id ]; - if ( c.thisStateVer === this.version - /*&& c.baseStateVer === o.baseObject.baseStateVersion - && c.refStateVer === o.referenceStateVersion*/ ) { + a = wls.length - 2; + mu = 1; - console.log( "ShipState.getObjectState: Using cache." ); - return c.state; + } } - } + //Try to do linear interpolation between closest data waterlines, but handle null values well: + let wl = new Array( sts.length ); + for ( let j = 0; j < wl.length; j ++ ) { - console.log( "ShipState.getObjectState: Not using cache." ); + let lower, upper; + let b = a; + //Find lower value for interpolation + if ( tab[ b ][ j ] !== null && ! isNaN( tab[ b ][ j ] ) ) { - let state = {}; - Object.assign( state, o.baseObject.baseState ); - Object.assign( state, o.referenceState ); - let oo = this.objectOverrides; - let sources = [ oo.common, oo.baseByID[ o.baseObject.id ], oo.derivedByGroup[ o.affiliations.group ], oo.derivedByID[ o.id ] ]; - for ( let i = 0; i < sources.length; i ++ ) { + lower = tab[ b ][ j ]; - let s = sources[ i ]; - if ( ! s ) continue; - let sk = Object.keys( s ); - for ( let k of sk ) { + } else { - //Override existing properties only: - if ( state[ k ] !== undefined ) { + b = a + 1; + while ( b < wls.length && ( isNaN( tab[ b ][ j ] ) || tab[ b ][ j ] === null ) ) { - state[ k ] = s[ k ]; + b ++; - } + } - } + if ( b !== wls.length ) { - } + //Inner NaN + lower = 0; - this.objectCache[ o.id ] = { - thisStateVer: this.version, - /*baseStateVer: o.baseObject.baseStateVersion, - refStateVer: o.referenceStateVersion,*/ - state: state - }; + } else { + + //Upper NaN, search below: + b = a - 1; + while ( b >= 0 && ( isNaN( tab[ b ][ j ] ) || tab[ b ][ j ] === null ) ) { + + b --; + + } + + if ( b === - 1 ) { + + //No number found: + lower = 0; + upper = 0; + + } else { + + lower = tab[ b ][ j ]; + upper = lower; + + } + + } + + } + + //Find upper value for interpolation + let c = a + 1; + if ( upper !== undefined ) { /*upper found above*/ } else if ( tab[ c ][ j ] !== null && ! isNaN( tab[ c ][ j ] ) ) { - return state; + upper = tab[ c ][ j ]; - } + } else { - //o is an object, k is a key to a single state property - getObjectStateProperty( o, k ) { + //The cell value is NaN. + //Upper is not defined. + //That means either tab[a][j] is a number + //or tab[a][j] is an inner NaN and + //there exists at least one number above it. + //In both cases I have to check above a+1. + c = a + 2; + while ( c < wls.length && ( isNaN( tab[ c ][ j ] ) || tab[ c ][ j ] === null ) ) { - return this.getObjectState( o )[ k ]; - //I have commented out a compact, but not very efficient, implementation of Alejandro's pattern, that does not fit too well with my caching solution. - /* let oo = this.objectOverrides; - let sources = [oo.derivedByID[o.id], oo.derivedByGroup[o.affiliations.group], oo.baseByID[o.baseObject.id], oo.common, o.getReferenceState(), o.baseObject.getBaseState()].filter(e=>!!e); - for (let i = 0; i < sources.length; i++) { - if (sources[i][k] !== undefined) return sources[i][k]; - } - return; //undefined*/ + c ++; - } + } - setFromSpecification( spec ) { + if ( c === wls.length ) upper = lower; + else { - this.setInitialState(); + upper = tab[ c ][ j ]; - this.objectCache = {}; //reset cache - if ( ! spec ) { + } - Object.assign( this, { - calculationParameters: {}, - //Named overrides because only existing corresponding properties will be set - objectOverrides: { - commmon: {}, - //baseByGroup: {}, - baseByID: {}, - derivedByGroup: {}, - derivedByID: {} } - } ); - return; - } + //Linear interpolation + wl[ j ] = lerp( lower, upper, mu ); + //Scale numerical values + if ( wl[ j ] !== null && ! isNaN( wl[ j ] ) ) wl[ j ] *= 0.5 * ha.BOA; - this.calculationParameters = spec.calculationParameters || {}; - this.objectOverrides = {}; - let oo = this.objectOverrides; - let soo = spec.objectOverrides || {}; - oo.common = soo.common || {}; - oo.baseByID = soo.baseByID || {}; - oo.derivedByGroup = soo.derivedByGroup || {}; - oo.derivedByID = soo.derivedByID || {}; + } - this.version ++; + return wl; - return this; + } - } + }, + //This must be debugged more. getWaterline got an overhaul, but this did not. + getStation: function ( x ) { + + let ha = this.attributes; + let xr = x / ha.LOA; + let sts = this.halfBreadths.stations; + let wls = this.halfBreadths.waterlines; + let tab = this.halfBreadths.table; - extend( spec ) { + let { index: a, mu: mu } = bisectionSearch( sts, xr ); - Object.assign( this.calculationParameters, spec.calculationParameters ); - this.calculatedProperties = {}; - let oo = this.objectOverrides; - let soo = spec.objectOverrides || {}; - Object.assign( oo.common, soo.common ); - let sources = [ soo.baseByID, soo.derivedByGroup, soo.derivedByID ]; - let targets = [ oo.baseByID, oo.derivedByGroup, oo.derivedByID ]; - for ( let i = 0; i < sources.length; i ++ ) { + let st; + if ( a < 0 || a >= sts.length ) st = new Array( wls.length ).fill( null ); + else if ( a + 1 === sts.length ) st = tab.map( row => row[ sts.length - 1 ] ); + else { - if ( ! sources[ i ] ) continue; - let sk = Object.keys( sources[ i ] ); - for ( let k of sk ) { + st = []; + for ( let j = 0; j < wls.length; j ++ ) { - if ( targets[ i ][ k ] !== undefined ) { + let after = tab[ j ][ a ]; + let forward = tab[ j ][ a + 1 ]; + if ( ( after === null || isNaN( after ) ) && ( forward === null || isNaN( forward ) ) ) { - Object.assign( targets[ i ][ k ], sources[ i ][ k ] ); + st.push( null ); } else { - targets[ i ][ k ] = sources[ i ][ k ]; + //Simply correcting by "|| 0" is not consistent with what is done in getWaterline. It may be better to correct upper nulls by nearest neighbor below. + st.push( lerp( after || 0, forward || 0, mu ) ); } @@ -1840,601 +1358,888 @@ class ShipState extends JSONSpecObject { } - this.version ++; + for ( let j = 0; j < this.halfBreadths.waterlines.length; j ++ ) { - } - //Applies only directives of spec that have a corresponding directive in this. - override( spec ) { + st[ j ] *= 0.5 * ha.BOA; + if ( isNaN( st[ j ] ) || st[ j ] === null ) st[ j ] = null; - let oo = this.objectOverrides; - let soo = spec.objectOverrides; + } - let sources = [ spec.calculationParameters, soo.common ]; - let targets = [ this.calculationParameters, oo.common ]; - for ( let i = 0; i < sources.length; i ++ ) { + return st; - if ( ! sources[ i ] ) continue; - let sk = Object.keys( sources[ i ] ); - for ( let k of sk ) { + }, + //typically deck bounds + waterlineCalculation: function ( z, bounds ) { - if ( targets[ i ][ k ] !== undefined ) { + let { minX, maxX, minY, maxY } = bounds || {}; - targets[ i ][ k ] = sources[ i ][ k ]; + //console.group/*Collapsed*/("waterlineCalculation."); + //console.info("Arguments: z=", z, " Boundaries: ", arguments[1]); - } + let wl = this.getWaterline( z ); + //console.info("wl: ", wl); //DEBUG - } + let LOA = this.attributes.LOA; + + let sts = this.halfBreadths.stations.slice(); + for ( let i = 0; i < sts.length; i ++ ) { + + sts[ i ] *= LOA; } - sources = [ soo.common, soo.baseByID, soo.derivedByGroup, soo.derivedByID ]; - targets = [ oo.common, oo.baseByID, oo.derivedByGroup, oo.derivedByID ]; + let hasMinX = ( minX !== undefined ) && minX !== sts[ 0 ]; + let hasMaxX = ( maxX !== undefined ) && maxX !== sts[ sts.length - 1 ]; + if ( hasMinX || hasMaxX ) { - for ( let i = 0; i < sources.length; i ++ ) { + let first = 0; + let wlpre; + if ( hasMinX ) { - if ( ! sources[ i ] ) continue; - let specKeys = Object.keys( sources[ i ] ); - for ( let key of specKeys ) { + let muf; + ( { index: first, mu: muf } = bisectionSearch( sts, minX ) ); + let lower = wl[ first ]; + let upper = wl[ first + 1 ]; + if ( ( lower === null || isNaN( lower ) ) && ( upper === null || isNaN( upper ) ) ) { - if ( targets[ i ][ key ] !== undefined ) { + wlpre = null; - let t = targets[ i ][ key ]; - let s = sources[ i ][ key ]; - if ( ! s ) continue; - let sk = Object.keys( s ); - //Loop over individual properties in assignments, and override only: - for ( let k of sk ) { + } else { - if ( t[ k ] !== undefined ) { + wlpre = lerp( lower || 0, upper || 0, muf ); - t[ k ] = s[ k ]; + } - } + } - } + let last = sts.length - 1; + let wlsuff; + if ( hasMaxX ) { + + let mul; + ( { index: last, mu: mul } = bisectionSearch( sts, maxX ) ); + let lower = wl[ last ]; + let upper = wl[ last + 1 ]; + if ( ( lower === null || isNaN( lower ) ) && ( upper === null || isNaN( upper ) ) ) { + + wlsuff = null; + + } else { + + wlsuff = lerp( lower || 0, upper || 0, mul ); } } - } + //Add virtual entries according to specified boundaries: + sts = sts.slice( first + 1, last + 1 ); + wl = wl.slice( first + 1, last + 1 ); + if ( hasMinX ) { - this.version ++; + sts.unshift( minX ); + wl.unshift( wlpre ); - } + } - setInitialState() { + if ( hasMaxX ) { - this.version = 0; - this.objectCache = {}; - this.continuous = {}; - this.discrete = {}; + sts.push( maxX ); + wl.push( wlsuff ); - } + } -} + } -//@EliasHasle + //This does not yet account properly for undefined minY, maxY. + let port = [], star = []; + for ( let i = 0; i < wl.length; i ++ ) { + if ( wl[ i ] === null || isNaN( wl[ i ] ) ) { -function loadShip( url, callback ) { + star[ i ] = minY || null; + port[ i ] = maxY || null; - // Use the common function for loading data via XMLHttpRequest - loadXMLHttpRequest( url, function ( specification ) { + } else { - let ship = new Ship( specification ); - callback( ship ); + star[ i ] = Math.max( - wl[ i ], minY || - wl[ i ] ); + port[ i ] = Math.min( wl[ i ], maxY || wl[ i ] ); - } ); + } -} + } + //DEBUG + //console.info("Arguments to sectionCalculation:", sts, star, port); -function loadXMLHttpRequest( url, callback, asyncMethod = true ) { + //sectionCalculation can potentially be served some nulls. + let sc = sectionCalculation( { xs: sts, ymins: star, ymaxs: port } ); + let LWL = sc.maxX - sc.minX; + let BWL = sc.maxY - sc.minY; + let Cwp = sc.A / ( LWL * BWL ); + let APP = this.attributes.APP || sc.minX; + let FPP = this.attributes.FPP || sc.maxX; + let LBP = FPP - APP; - let request = new XMLHttpRequest(); - request.open( "GET", url, asyncMethod ); - request.addEventListener( "load", function ( event ) { + let output = { + z: z, + xc: sc.xc, + yc: sc.yc, + Awp: sc.A, + Ix: sc.Ix, + Iy: sc.Iy, + maxX: sc.maxX, + minX: sc.minX, + maxY: sc.maxY, + minY: sc.minY, + Cwp: Cwp, + LWL: LWL, + LBP: LBP, + BWL: BWL + }; + //console.info("Output from waterlineCalculation: ", output); + //console.groupEnd(); + return output; - if ( request.status === 200 ) { + }, + //Not done, and not tested + //The optional maxZ parameter is introduced for enabling below-water calculations. More bounds will add more complexity, although then some common logic may perhaps be moved from this method and waterlineCalculation to sectionCalculation. + stationCalculation: function ( x, maxZ ) { + + let wls = this.halfBreadths.waterlines.map( wl => this.attributes.Depth * wl ); + let port = this.getStation( x ); + if ( maxZ !== null && ! isNaN( maxZ ) ) { - let response = event.target.response; - var specification = JSON.parse( response ); - callback( specification ); + let { index, mu } = bisectionSearch( wls, maxZ ); + if ( index < wls.length - 1 ) { - } else { + wls[ index + 1 ] = lerp( wls[ index ], wls[ index + 1 ], mu ); + port[ index + 1 ] = lerp( port[ index ], port[ index + 1 ], mu ); + wls = wls.slice( 0, index + 2 ); + port = port.slice( 0, index + 2 ); - console.error( "Error loading data from: " + url ); + } } - } ); - request.send( null ); - -} + let star = port.map( hb => - hb ); -//@EliasHasle -//@ferrari212 + let sc = sectionCalculation( { xs: wls, ymins: star, ymaxs: port } ); + return { + x: x, //or xc? or cg.. Hm. + yc: sc.yc, + zc: sc.xc, + A: sc.A, + Iz: sc.Ix, + Iy: sc.Iy, + maxZ: sc.maxX, + minZ: sc.minX, + maxY: sc.maxY, + minY: sc.minY + }; + }, -class Ship extends JSONSpecObject { + /* + Known issues: + nulls in the offset table will be corrected to numbers in this calculation, whereas the intended meaning of a null supposedly is that there is no hull at that position. This means the calculation can overestimate the wetted area (and possibly make other errors too). + */ + //Important: calculateAttributesAtDraft takes one mandatory parameter T. (The function defined here is immediately called during construction of the prototype, and returns the proper function.) + calculateAttributesAtDraft: function () { - constructor( specification ) { + function levelCalculation( hull, + z, + prev = { + z: 0, + Vs: 0, + Vbb: 0, + As: 0, + minX: 0, + maxX: 0, + minY: 0, + maxY: 0, + prMinY: 0, + prMaxY: 0, + Ap: 0, + Cv: { x: 0, y: 0, z: 0 } + } ) { - // Check if the Specification is a string pointing - // to a JSON file. In case of positive answer, load - // the file and parse it to JSON + let wlc = hull.waterlineCalculation( z, {} ); + let lev = {}; + Object.assign( lev, wlc ); + //Projected area calculation (approximate): + lev.prMinY = wlc.minY; + lev.prMaxY = wlc.maxY; + //DEBUG: + //console.info("prev.Ap = ", prev.Ap); + //console.info("Parameters to trapezoidCalculation: (%.2f, %.2f, %.2f, %.2f, %.2f, %.2f)", prev.prMinY, prev.prMaxY, lev.prMinY, lev.prMaxY, prev.z, z); + let AT = trapezoidCalculation( prev.prMinY, prev.prMaxY, lev.prMinY, lev.prMaxY, prev.z, z )[ "A" ]; + //console.log("Calculated area of trapezoid: ", AT); + lev.Ap = prev.Ap + AT; + //lev.Ap = prev.Ap + // + trapezoidCalculation(prev.prMinY, prev.prMaxY, lev.prMinY, lev.prMaxY, prev.z, z)["A"]; + //DEBUG END - // Warning: this uses an async function for XMLHttpRequest - // this function is deprecated and it is not recommended for - // slow connections. Its purpose is to ease the usability - // of the library without having to import the loader in the main script. - // For further information on synchronous request, please read: - // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests#synchronous_request - // @ferrari212 - if ( typeof specification === "string" ) { - loadXMLHttpRequest( specification, function ( parsedJSON ) { + //level bounds are for the bounding box of the submerged part of the hull + if ( wlc.minX !== null && ! isNaN( wlc.minX ) && wlc.minX <= prev.minX ) + lev.minX = wlc.minX; + else + lev.minX = prev.minX; + if ( wlc.maxX !== null && ! isNaN( wlc.maxX ) && wlc.maxX >= prev.maxX ) + lev.maxX = wlc.maxX; + else + lev.maxX = prev.maxX; + if ( wlc.minY !== null && ! isNaN( wlc.minY ) && wlc.minY <= prev.minY ) + lev.minY = wlc.minY; + else + lev.minY = prev.minY; + if ( wlc.maxY !== null && ! isNaN( wlc.maxY ) && wlc.maxY >= prev.maxY ) + lev.maxY = wlc.maxY; + else + lev.maxY = prev.maxY; - specification = parsedJSON; + lev.Vbb = ( lev.maxX - lev.minX ) * ( lev.maxY - lev.minY ) * z; - }, false ); + //Keep level maxX and minX for finding end cap areas: + lev.maxXwp = wlc.maxX; + lev.minXwp = wlc.minX; - } + //Find bilinear patches in the slice, and combine them. + //Many possibilities for getting the coordinate systems wrong. + let calculations = []; + let sts = hull.halfBreadths.stations.map( st => st * hull.attributes.LOA ); + let wl = hull.getWaterline( z ); + let prwl = hull.getWaterline( prev.z ); + for ( let j = 0; j < sts.length - 1; j ++ ) { + let port = + patchColumnCalculation( sts[ j ], sts[ j + 1 ], prev.z, z, - prwl[ j ], - wl[ j ], - prwl[ j + 1 ], - wl[ j + 1 ] ); + calculations.push( port ); + let star = + patchColumnCalculation( sts[ j ], sts[ j + 1 ], prev.z, z, prwl[ j ], wl[ j ], prwl[ j + 1 ], wl[ j + 1 ] ); + calculations.push( star ); - super( specification ); + } - } + //console.log(calculations); //DEBUG + let C = combineVolumes( calculations ); + //Cv of slice. Note that switching of yz must + //be done before combining with previous level + let Cv = { x: C.Cv.x, y: C.Cv.z, z: C.Cv.y }; - createShip3D( specs, Ship3D ) { + lev.Vs = prev.Vs + C.V; //hull volume below z + lev.As = prev.As + C.As; //outside surface below z + //End caps: + if ( lev.minXwp <= sts[ 0 ] ) + lev.As += hull.stationCalculation( lev.minXwp, z )[ "A" ]; + if ( lev.maxXwp >= sts[ sts.length - 1 ] ) + lev.As += hull.stationCalculation( lev.maxXwp, z )[ "A" ]; - const state = this.designState; - try { + //center of volume below z (some potential for accumulated rounding error when calculating an accumulated average like this): + lev.Cv = Vectors.scale( Vectors.add( + Vectors.scale( prev.Cv, prev.Vs ), + Vectors.scale( Cv, C.V ) + ), 1 / ( lev.Vs || 2 ) ); - // Initialize the - this.ship3D = new Ship3D( this, Object.assign( { - shipState: state, - }, specs ) - ); + lev.Cb = lev.Vs / lev.Vbb; + lev.Cp = lev.Vs / ( lev.Ap * ( lev.maxX - lev.minX ) ); - } catch ( error ) { + return lev; - const error_string = "Ship3D is not a constructor"; + } - if ( error.message.includes( error_string ) ) { + //Here is the returned function calculateAttributesAtDraft(T): + return function ( T ) { - const error_reason = "The Ship3D function passed as argument is in a wrong format"; - const error_solution = "Please, verify if the argument was imported correctly on the parent function: import { Ship3D } from 'path/to/file'"; + if ( T === null || isNaN( T ) ) { - console.error( `${error_reason}: ${error.message}. ${error_solution}` ); + console.error( "Hull.prototype.calculateAttributesAtDraft(T): No draft specified. Returning undefined." ); + return; - } else { + } else if ( T < 0 || T > this.attributes.Depth ) { - console.error( error ); + console.error( "Hull.prototype.calculateAttributesAtDraft(T): Draft parameter " + T + "outside valid range of [0,Depth]. Returning undefined." ); } - } + let wls = this.halfBreadths.waterlines.map( wl => this.attributes.Depth * wl ); + //This is the part that can be reused as long as the geometry remains unchanged: + if ( this.levelsNeedUpdate ) { - } + this.levels = []; + for ( let i = 0; i < wls.length; i ++ ) { - calculateDraft( shipState, epsilon = 0.001, rho = 1025 ) { + let z = wls[ i ]; + let lev = levelCalculation( this, z, this.levels[ i - 1 ] ); + //Bottom cap, only on the lowest level: + if ( i === 0 ) { - let w = this.getWeight( shipState ); - let M = w.mass; - return this.structure.hull.calculateDraftAtMass( M, epsilon, rho ); + lev.As += lev.Awp; - } + } - calculateStability( shipState ) { + this.levels.push( lev ); - let w = this.getWeight( shipState ); - let KG = w.cg.z; - let LCG = w.cg.x; - let T = this.structure.hull.calculateDraftAtMass( w.mass ); - let { BMt, BMl, KB, LCB, LCF, LWL, BWL } = this.structure.hull.calculateAttributesAtDraft( T ); - let GMt = KB + BMt - KG; - let GMl = KB + BMl - KG; + } - // avaiable just for small angles < 3° - // this calculation can be incorporated to Vessesl.js with no problem - let trim = ( LCB - LCG ) / GMl; - let draftfp = 0; - let draftap = 0; - let trimd = 0; + this.levelsNeedUpdate = false; - if ( trim < Math.tan( 3 * Math.PI / 180 ) ) { + } - draftfp = T - ( LWL - LCF ) * trim; - draftap = T + ( LCF ) * trim; - trimd = draftfp - draftap; + //Find highest data waterline below or at water level: + let { index, mu } = bisectionSearch( wls, T ); + + //console.info("Highest data waterline below or at water level: " + index); + //console.log(this.levels); + let lc; + if ( mu === 0 ) lc = this.levels[ index ]; + else lc = levelCalculation( this, T, this.levels[ index ] ); + + //Filter and rename for output + return { + xcwp: lc.xc, //water plane values + LCF: lc.xc, + ycwp: lc.yc, + TCF: lc.yc, + Awp: lc.Awp, + Ixwp: lc.Ix, + BMt: lc.Ix / lc.Vs, + Iywp: lc.Iy, + BMl: lc.Iy / lc.Vs, + maxXs: lc.maxX, //boundaries of the submerged part of the hull + minXs: lc.minX, + maxYs: lc.maxY, + minYs: lc.minY, + Cwp: lc.Cwp, + LWL: lc.LWL, + LBP: lc.LBP, + BWL: lc.BWL, + Ap: lc.Ap, //projected area in length direction + Cp: lc.Cp, //prismatic coefficient + //Vbb: lc.Vbb, + Vs: lc.Vs, //volume of submerged part of the hull + Cb: lc.Cb, + Cm: lc.Cb / lc.Cp, + As: lc.As, //wetted area + Cv: lc.Cv, //center of buoyancy + LCB: lc.Cv.x, + TCB: lc.Cv.y, + KB: lc.Cv.z + }; - } else { + }; - draftfp = null; - draftap = null; - trimd = null; + }(), + //M is the mass (in kg) of the ship + calculateDraftAtMass: function ( M, epsilon = 0.001, rho = 1025 ) { - } + let VT = M / rho; //Target submerged volume (1025=rho_seawater) + //Interpolation: + let a = 0; + let b = this.attributes.Depth; //depth is not draft ¿? + let t = 0.5 * b; + //Souce: https://en.wikipedia.org/wiki/Secant_method + // Secant Method to Find out where is the zero point + // Used to find out the Draft but can be generalized + let V1 = 0 - VT; + let V2 = VT; //Just inserting V2 an ordinary value to not have to calculate it twice + let n = 0; + while ( Math.abs( t - a ) > epsilon ) { - //change the trim for angles - trim = Math.atan( trim ) * 180 / Math.PI; - console.log( trimd ); + //This following condition force just receive draft from [0;Depth] + if ( t > b ) { - let heel = w.cg.y / GMt; - //change the hell for meters - heel *= BWL; + t = b; - return { w, T, GMt, GMl, KB, BMt, BMl, KG, trim, draftfp, draftap, trimd, heel }; + } - } + V2 = this.calculateAttributesAtDraft( t )[ "Vs" ] - VT; + // debugger + let dx = ( V2 - V1 ) / ( t - a ); + if ( dx > 0.1 || dx < - 0.1 ) { - getFuelMass( shipState ) { + a = t; + V1 = V2; + t = t - V2 / dx; + //In case the derived of function is close to 0 we can follow the Bisection method + //Source: https://en.wikipedia.org/wiki/Bisection_method - shipState = shipState || this.designState; + } else { - let fuelMass = {}; - fuelMass.totalMass = 0; - fuelMass.tankMass = {}; - fuelMass.tankStates = {}; - for ( let o of Object.values( this.derivedObjects ) ) { + let ts = 0.5 * ( a + t ); //intermediate point + let Vs = this.calculateAttributesAtDraft( ts )[ "Vs" ] - VT; //this values must be calculated twice, see better example + if ( Vs > 0 ) { - if ( o.affiliations.group === "fuel tanks" ) { + t = ts; + V2 = Vs; - fuelMass.tankStates[ o.id ] = shipState.getObjectState( o ); - fuelMass.tankMass[ o.id ] = o.baseObject.weightInformation.contentDensity * o.baseObject.weightInformation.volumeCapacity * fuelMass.tankStates[ o.id ].fullness; - fuelMass.totalMass += fuelMass.tankMass[ o.id ]; + } else { + + a = ts; + V1 = Vs; + + } } } - return fuelMass; + return t; } +} ); +//@EliasHasle +//@EliasHasle - subtractFuelMass( mass, shipState ) { +/* +Depends on JSONSpecObject.js +*/ - shipState = shipState || this.designState; +function BaseObject( specification ) { - var fuelMass = this.getFuelMass( shipState ); - var totalFuel = fuelMass.totalMass; - var tankEntr = Object.entries( fuelMass.tankMass ); + this.weightCache = {}; + JSONSpecObject.call( this, specification ); - var fuelCap = 0; - for ( var tk = 0; tk < tankEntr.length; tk ++ ) { +} - var tkId = tankEntr[ tk ][ 0 ]; - fuelCap += this.derivedObjects[ tkId ].baseObject.weightInformation.volumeCapacity * this.derivedObjects[ tkId ].baseObject.weightInformation.contentDensity; +BaseObject.prototype = Object.create( JSONSpecObject.prototype ); +Object.assign( BaseObject.prototype, { + constructor: BaseObject, + setFromSpecification: function ( spec ) { - } + this.id = spec.id; + this.affiliations = spec.affiliations || {}; + this.boxDimensions = spec.boxDimensions || { length: undefined, width: undefined, height: undefined }; + this.weightInformation = spec.weightInformation; + this.cost = spec.cost || { currency: undefined, value: undefined }; + this.capabilities = spec.capabilities || {}; + this.file3D = spec.file3D; + this.baseState = spec.baseState; + return this; - // check if tanks have necessary fuel - if ( mass <= totalFuel ) { // if yes, subtract mass in the same proportion + }, + getSpecification: function () { - for ( var tk = 0; tk < tankEntr.length; tk ++ ) { + return { + id: this.id, + affiliations: this.affiliations, + boxDimensions: this.boxDimensions, + weightInformation: this.weightInformation, + cost: this.cost, + capabilities: this.capabilities, + file3D: this.file3D, + baseState: this.baseState + }; - var tkId = tankEntr[ tk ][ 0 ]; - shipState.objectCache[ tkId ].state.fullness -= mass / fuelCap; + }, + //Maybe this will take more state parameters than just fullness. + getWeight: function ( fullness ) { - } + fullness = fullness || 0; - } else { // if not, make tanks empty + let wi = this.weightInformation; + //Should maybe have been this.capabilities.weightInformation? - mass -= totalFuel; - for ( var tk = 0; tk < tankEntr.length; tk ++ ) { + //(Fluid) container properties default to no content: + let d = wi.contentDensity || 0; + let v = wi.volumeCapacity || 0; + //Maybe we should have another handling of cargo (with variable density) + + let m = wi.lightweight + d * v * fullness; + let cg; + if ( wi.fullnessCGMapping !== undefined ) { + + let fcgm = wi.fullnessCGMapping; + let fs = fcgm.fullnesses; + let cgs = fcgm.cgs; + //Find closest entries: + let { index: i, mu: mu } = bisectionSearch( fs, fullness ); + cg = []; + for ( let j = 0; j < 3; j ++ ) { - var tkId = tankEntr[ tk ][ 0 ]; - shipState.objectCache[ tkId ].state.fullness = 0; + let c; + if ( i < fs.length - 1 ) + //Linear interpolation between closest entries: + c = lerp( cgs[ i ][ j ], cgs[ i + 1 ][ j ], mu ); + else c = cgs[ i ][ j ]; + //if (c===null || isNaN(c)) console.error("BaseObject.getWeight: Invalid value found after interpolation."); + cg.push( c ); } - console.error( "Vessel ran out of fuel before " + mass.toFixed( 2 ) + " tons were subtracted." ); + } else if ( wi.cg !== undefined ) { - } + //console.log("BaseObject.getWeight: Using specified cg."); + cg = wi.cg; - // update related states. In the future, make this consistent with improved caching system - for ( var prop in shipState.objectCache ) { + } else { - shipState.objectCache[ prop ].thisStateVer ++; + console.warn( "BaseObject.getWeight: No cg or fullnessCGMapping supplied. Defaults to center of bounding box." ); + cg = [ 0, 0, 0.5 * this.boxDimensions.height ]; } - shipState.version ++; + let w = { mass: m, cg: { x: cg[ 0 ], y: cg[ 1 ], z: cg[ 2 ] } }; + return w; } +} ); +//@EliasHasle - setFromSpecification( specification ) { - - - this.attributes = specification.attributes || {}; - this.structure = new Structure( specification.structure/*,this*/ ); - //baseObjects and derivedObjects are arrays in the specification, but are objects (hashmaps) in the constructed ship object: - this.baseObjects = {}; - for ( let i = 0; i < specification.baseObjects.length; i ++ ) { +/* +Depends on JSONSpecObject.js +*/ - let os = specification.baseObjects[ i ]; - this.baseObjects[ os.id ] = new BaseObject( os ); +function DerivedObject( specification, baseObjects ) { - } + this.baseObjects = baseObjects; + JSONSpecObject.call( this, specification ); - this.derivedObjects = {}; - for ( let i = 0; i < specification.derivedObjects.length; i ++ ) { +} - let os = specification.derivedObjects[ i ]; - this.derivedObjects[ os.id ] = new DerivedObject( os, this.baseObjects ); +DerivedObject.prototype = Object.create( JSONSpecObject.prototype ); +Object.assign( DerivedObject.prototype, { + constructor: DerivedObject, + setFromSpecification: function ( spec ) { - } + this.id = spec.id; + this.group = spec.group || null; + this.affiliations = spec.affiliations; + if ( typeof spec.baseObject === "string" ) { - this.designState = new ShipState( specification.designState ); - return this; + this.baseObject = this.baseObjects[ spec.baseObject ]; + } else { - } + this.baseObject = new BaseObject( spec.baseObject ); - getSpecification() { + } - let specification = {}; - specification.attributes = this.attributes; - specification.structure = this.structure.getSpecification(); + this.referenceState = spec.referenceState; + //this.referenceStateVersion = 0; + this.style = spec.style || {}; + return this; - specification.baseObjects = Object.values( this.baseObjects ).map( o => o.getSpecification() ); - specification.derivedObjects = Object.values( this.derivedObjects ).map( o => o.getSpecification() ); + }, + getSpecification: function () { - specification.designState = this.designState.getSpecification(); + let spec = { + id: this.id, + group: this.group, + affiliations: this.affiliations, + referenceState: this.referenceState, + style: this.style + }; + if ( this.baseObjects[ this.baseObject.id ] !== undefined ) { - return specification; + spec.baseObject = this.baseObject.id; - } + } else { - getWeight( shipState ) { + spec.baseObject = this.baseObject.getSpecification(); - shipState = shipState || this.designState; + } - // Comment: This piece of code maybe must be deleted - // this.structure = new Structure( specification.structure/*,this*/ ); + return spec; - let components = []; + }, + getWeight: function ( state ) { - components.push( - this.structure.getWeight( this.designState ) - ); + let oState = state.getObjectState( this ); - for ( let o of Object.values( this.derivedObjects ) ) { + //Support disabled objects: + if ( oState.exists === false ) { - components.push( o.getWeight( shipState ) ); + return { mass: 0, cg: { x: 0, y: 0, z: 0 } }; } - var W = combineWeights( components ); - //console.info("Calculated weight object: ", W); - return W; - - } + let p = { + x: oState.xCentre, + y: oState.yCentre, + z: oState.zBase + }; - // This function is for sanity checking if objects states and id are compatible - validateObjectBond( objects, id ) { + let w = this.baseObject.getWeight( oState.fullness ); + let m = w.mass; + let cg = Vectors.add( p, w.cg ); - if ( typeof id !== "string" || objects[ id ] === undefined ) { + if ( isNaN( cg.x + cg.y + cg.z ) ) { - console.error( "Undefined bond with ID '" + id + "' in the object" ); - throw new Error( "validateObjectBond: object bond modification not valid" ); + console.error( "DerivedObject.getWeight: returning NaN values." ); } - } - - changeObject( object, spec ) { - - if ( typeof object !== "object" ) { + return { mass: m, cg: cg }; - console.error( "changeObject: object must be an object." ); - return; + } +} ); +//@EliasHasle - } +/* +The object state assignments could/should also have a baseByGroup. A group of baseObjects could for instance be a category including all tanks that carry a given compound, regardless of their size and shape. Maybe "group" is not a good name for something that can be set freely. Maybe "label" or "tag" or something else. The same goes for derivedByGroup. - if ( typeof spec !== "object" ) { +With this, there would be five types of assignments: +common: All objects. +baseByGroup: Applies to every object that has its base object's property "group" set to the given name. +baseByID: Applies to all objects that have base object consistent with the given ID: +derivedByGroup: Applies to every object that has its property "group" set to the given name. +derivedByID: Applies only to the object with given ID. - console.error( "changeObject: spec must be an object." ); - return; +Assignments of subsequent types override assignments of previous types. +*/ - } +/* +The caching and version control is clumsy (and incomplete). I (Elias) have done some separate testing of ways to do it properly. This must be implemented later. +*/ - for ( const key in spec ) { +/* +ShipState now mainly accounts for load state, by which I mean the states of objects in the ship. We need to find out how to best handle other state properties, like global position, heading etc., not to mention properties that change fast, and that depend on time and current state (motion fluctuations etc.). +*/ - // this ensures all the keys are reassigned to the object - const previous_spec = object.getSpecification()[ key ]; +function ShipState( specification ) { - const new_spec = {}; - new_spec[ key ] = Object.assign( previous_spec, spec[ key ] ); + this.version = 0; + this.objectCache = {}; + this.continuous = {}; + this.discrete = {}; + JSONSpecObject.call( this, specification ); - Object.assign( object, new_spec ); +} - } +ShipState.prototype = Object.create( JSONSpecObject.prototype ); +Object.assign( ShipState.prototype, { + constructor: ShipState, + getSpecification: function () { - } + if ( this.cachedVersion !== this.version ) { - getBaseObjectById( id = undefined ) { + var spec = { + calculationParameters: this.calculationParameters, + objectOverrides: this.objectOverrides//{} + }; - if ( id === undefined ) { + //Sketchy, but versatile: + spec = JSON.parse( JSON.stringify( spec ) ); - return this.baseObjects; + this.specCache = spec; + this.cachedVersion = this.version; } - const ids_array = Array.isArray( id ) ? id : [ id ]; + return this.specCache; - let obj = {}; + }, + clone: function () { - for ( let id of ids_array ) { + return new ShipState( this.getSpecification() ); - const baseObject = this.baseObjects[ id ]; - if ( baseObject ) { + }, + getObjectState: function ( o ) { - obj[ id ] = baseObject; + if ( this.objectCache[ o.id ] !== undefined ) { - } else { + let c = this.objectCache[ o.id ]; + if ( c.thisStateVer === this.version + /*&& c.baseStateVer === o.baseObject.baseStateVersion + && c.refStateVer === o.referenceStateVersion*/ ) { - console.warn( `BaseObject with id ${id} not found.` ); + console.log( "ShipState.getObjectState: Using cache." ); + return c.state; } - } - return obj; - - } - - - changeBaseObjectById( id, spec ) { - - this.validateObjectBond( this.baseObjects, id ); - - this.changeObject( this.baseObjects[ id ], spec ); + console.log( "ShipState.getObjectState: Not using cache." ); - } + let state = {}; + Object.assign( state, o.baseObject.baseState ); + Object.assign( state, o.referenceState ); + let oo = this.objectOverrides; + let sources = [ oo.common, oo.baseByID[ o.baseObject.id ], oo.derivedByGroup[ o.affiliations.group ], oo.derivedByID[ o.id ] ]; + for ( let i = 0; i < sources.length; i ++ ) { - changeDerivedObjectById( id, spec ) { + let s = sources[ i ]; + if ( ! s ) continue; + let sk = Object.keys( s ); + for ( let k of sk ) { - this.validateObjectBond( this.derivedObjects, id ); + //Override existing properties only: + if ( state[ k ] !== undefined ) { - this.changeObject( this.derivedObjects[ id ], spec ); + state[ k ] = s[ k ]; - if ( "baseObject" in spec ) { + } - console.error( "Key 'baseObject' identified under the spec. Please, use change changeBaseObjectById if you want to change the base object." ); - return; + } } - } - - deleteBaseObjectById( id ) { + this.objectCache[ o.id ] = { + thisStateVer: this.version, + /*baseStateVer: o.baseObject.baseStateVersion, + refStateVer: o.referenceStateVersion,*/ + state: state + }; - this.validateObjectBond( this.baseObjects, id ); + return state; - delete this.baseObjects[ id ]; + }, + //o is an object, k is a key to a single state property + getObjectStateProperty: function ( o, k ) { - // Check if an derivedObject is using the deleted object as baseObject - // Delete it in case it is - for ( const [ key, o ] of Object.entries( this.derivedObjects ) ) { + return this.getObjectState( o )[ k ]; + //I have commented out a compact, but not very efficient, implementation of Alejandro's pattern, that does not fit too well with my caching solution. + /* let oo = this.objectOverrides; + let sources = [oo.derivedByID[o.id], oo.derivedByGroup[o.affiliations.group], oo.baseByID[o.baseObject.id], oo.common, o.getReferenceState(), o.baseObject.getBaseState()].filter(e=>!!e); + for (let i = 0; i < sources.length; i++) { + if (sources[i][k] !== undefined) return sources[i][k]; + } + return; //undefined*/ - if ( o.baseObject.id === id ) { + }, + //Sets this state exclusively from parameter. + setFromSpecification: function ( spec ) { - this.deleteDerivedObjectById( key ); + this.objectCache = {}; //reset cache + if ( ! spec ) { - } + Object.assign( this, { + calculationParameters: {}, + //Named overrides because only existing corresponding properties will be set + objectOverrides: { + commmon: {}, + //baseByGroup: {}, + baseByID: {}, + derivedByGroup: {}, + derivedByID: {} + } + } ); + return; } - } - - deleteDerivedObjectById( id ) { - - this.validateObjectBond( this.derivedObjects, id ); + this.calculationParameters = spec.calculationParameters || {}; + this.objectOverrides = {}; + let oo = this.objectOverrides; + let soo = spec.objectOverrides || {}; + oo.common = soo.common || {}; + oo.baseByID = soo.baseByID || {}; + oo.derivedByGroup = soo.derivedByGroup || {}; + oo.derivedByID = soo.derivedByID || {}; - delete this.derivedObjects[ id ]; + this.version ++; - } + return this; - addNewObject( spec ) { + }, + //Overrides existing directives and adds new ones. + extend: function ( spec ) { - if ( typeof spec !== "object" ) { + Object.assign( this.calculationParameters, spec.calculationParameters ); + this.calculatedProperties = {}; + let oo = this.objectOverrides; + let soo = spec.objectOverrides || {}; + Object.assign( oo.common, soo.common ); + let sources = [ soo.baseByID, soo.derivedByGroup, soo.derivedByID ]; + let targets = [ oo.baseByID, oo.derivedByGroup, oo.derivedByID ]; + for ( let i = 0; i < sources.length; i ++ ) { - console.error( "changeObject: spec must be an object." ); - return; + if ( ! sources[ i ] ) continue; + let sk = Object.keys( sources[ i ] ); + for ( let k of sk ) { - } + if ( targets[ i ][ k ] !== undefined ) { - if ( ! Array.isArray( spec.baseObject ) || ! Array.isArray( spec.derivedObjects ) ) { + Object.assign( targets[ i ][ k ], sources[ i ][ k ] ); - console.error( "addObject: spec.baseObject must be an array." ); - return; + } else { - } + targets[ i ][ k ] = sources[ i ][ k ]; - for ( let baseObject of spec.baseObject ) { + } - this.baseObjects[ baseObject.id ] = new BaseObject( baseObject ); + } } - for ( let derivedObject of spec.derivedObjects ) { - - this.validateObjectBond( this.baseObjects, derivedObject.id ); + this.version ++; - this.derivedObjects[ derivedObject.id ] = new DerivedObject( derivedObject, this.baseObjects ); + }, + //Applies only directives of spec that have a corresponding directive in this. + override: function ( spec ) { - } + let oo = this.objectOverrides; + let soo = spec.objectOverrides; - } + let sources = [ spec.calculationParameters, soo.common ]; + let targets = [ this.calculationParameters, oo.common ]; + for ( let i = 0; i < sources.length; i ++ ) { -} + if ( ! sources[ i ] ) continue; + let sk = Object.keys( sources[ i ] ); + for ( let k of sk ) { -class WaveCreator { + if ( targets[ i ][ k ] !== undefined ) { - constructor( defList, waveDuration = 3600 ) { + targets[ i ][ k ] = sources[ i ][ k ]; - this.waveDef = {}; // store wave definition - this.version = 0; // version counter for memorisation pattern - this.defList = defList || [ // list wave definitions - [ 0.6, 2.25, 180 ], - [ 1.1, 1.75, 150 ] - ]; + } - // set a regular wave definition - this.setWaveDef = function setWaveDef( freq, amp, head ) { + } - var newWaveDef; - newWaveDef = { - waveFreq: freq, // angular frequency - waveAmplitude: amp, - heading: head // 0 to 360. 180 corresponds to head seas - }; - if ( JSON.stringify( this.waveDef ) !== JSON.stringify( newWaveDef ) ) { + } - this.waveDef = newWaveDef; - this.version ++; + sources = [ soo.common, soo.baseByID, soo.derivedByGroup, soo.derivedByID ]; + targets = [ oo.common, oo.baseByID, oo.derivedByGroup, oo.derivedByID ]; - } + for ( let i = 0; i < sources.length; i ++ ) { - }; + if ( ! sources[ i ] ) continue; + let specKeys = Object.keys( sources[ i ] ); + for ( let key of specKeys ) { - this.setRandom = function () { // set a wave definition randomly + if ( targets[ i ][ key ] !== undefined ) { - var rand = this.defList[ Math.floor( Math.random() * this.defList.length ) ]; - this.setWaveDef( rand[ 0 ], rand[ 1 ], rand[ 2 ] ); + let t = targets[ i ][ key ]; + let s = sources[ i ][ key ]; + if ( ! s ) continue; + let sk = Object.keys( s ); + //Loop over individual properties in assignments, and override only: + for ( let k of sk ) { - }; + if ( t[ k ] !== undefined ) { - this.setTime = function ( time ) { + t[ k ] = s[ k ]; - // change wave definition along time - var noSpans = time / this.waveDuration; - var coord = Math.floor( noSpans % this.defList.length ); + } - this.setWaveDef( this.defList[ coord ][ 0 ], this.defList[ coord ][ 1 ], this.defList[ coord ][ 2 ] ); + } - }; + } - } + } -} + } -class StateModule { + this.version ++; - constructor( ship, states ) { + } +} ); +function StateModule( ship, states ) { - this.ship = ship; - this.states = states; + this.ship = ship; + this.states = states; - } +} - returnOutput() { +Object.assign( StateModule.prototype, { + // return getters listed on an output array inside the simulation object + returnOutput: function () { let resObj = {}; for ( let i = 0; i < this.output.length; i ++ ) { @@ -2454,10 +2259,9 @@ class StateModule { return resObj; - } - + }, // write getter output to shipState - writeOutput() { + writeOutput: function () { let stateName = this.constructor.name; if ( this.states.discrete[ stateName ] === undefined ) { @@ -2486,9 +2290,8 @@ class StateModule { this.states.discrete[ stateName ].thisStateVer ++; - } - - setDraft() { + }, + setDraft: function () { let draft = this.ship.calculateDraft( this.states ); if ( this.states.discrete.FloatingCondition === undefined ) { @@ -2504,11 +2307,9 @@ class StateModule { Object.assign( this.states.discrete.FloatingCondition.state, this.ship.calculateStability( this.states ) ); this.states.discrete.FloatingCondition.thisStateVer ++; - - } - + }, // write argument speed to vessel state. If undefined, use vessel's design speed - setSpeed( speed ) { + setSpeed: function ( speed ) { if ( this.states.discrete.Speed === undefined ) { @@ -2528,11 +2329,10 @@ class StateModule { this.states.discrete.Speed.state.speed = speed; // knots this.states.discrete.Speed.thisStateVer ++; - } - + }, // write argument heading angle to vessel state. if undefined, use 0 degrees // 0 degrees corresponds to vessel pointing to north. clockwise orientation. - setHeading( angle ) { + setHeading: function ( angle ) { if ( this.states.discrete.Heading === undefined ) { @@ -2552,11 +2352,10 @@ class StateModule { this.states.discrete.Heading.state.heading = angle; this.states.discrete.Heading.thisStateVer ++; - } - - // cache memoization pattern adapted from https://b-studios.de/javascript/languages/2013/11/18/lazy-attributes-in-ecmascript-5 + }, + // cache memoization pattern adapted from http://b-studios.de/blog/2013/11/18/lazy-attributes-in-ecmascript-5/ // in the future, expand version comparison also to parameters stored inside each constructor - memoized( init, cacheName ) { + memoized: function ( init, cacheName ) { return { enumerable: true, @@ -2640,1311 +2439,1415 @@ class StateModule { }; } +} ); +// simple function to output regular wave pattern +function WaveCreator( defList, waveDuration = 3600 ) { // wave creator constructor + this.waveDef = {}; // store wave definition + this.version = 0; // version counter for memorisation pattern + // set a regular wave definition + this.setWaveDef = function ( freq, amp, head ) { -} - -class WaveMotion extends StateModule { + var newWaveDef; + newWaveDef = { + waveFreq: freq, // angular frequency + waveAmplitude: amp, + heading: head // 0 to 360. 180 corresponds to head seas + }; + if ( JSON.stringify( this.waveDef ) !== JSON.stringify( newWaveDef ) ) { - constructor( ship, states, wavCre, position = 0, critDampPercentage = 20, g = 9.81, rho = 1025 ) { + this.waveDef = newWaveDef; + this.version ++; - super( ship, states ); + } - if ( typeof this.states.discrete.FloatingCondition === "undefined" ) { + }; - this.setDraft(); + this.defList = defList || [ // list wave definitions + [ 0.6, 2.25, 180 ], + [ 1.1, 1.75, 150 ] + ]; + this.setRandom = function () { // set a wave definition randomly - } + var rand = this.defList[ Math.floor( Math.random() * this.defList.length ) ]; + this.setWaveDef( rand[ 0 ], rand[ 1 ], rand[ 2 ] ); - if ( typeof this.states.discrete.Speed === "undefined" ) { // if vessel does not have a speed state + }; - this.setSpeed(); // use its design speed + this.setTime = function ( time ) { - } + // change wave definition along time + var noSpans = time / waveDuration; + var coord = Math.floor( noSpans % this.defList.length ); - if ( typeof this.states.discrete.Heading === "undefined" ) { + this.setWaveDef( this.defList[ coord ][ 0 ], this.defList[ coord ][ 1 ], this.defList[ coord ][ 2 ] ); - this.setHeading(); + }; - } +} +// adapted from http://www.shiplab.ntnu.co/app/shipmotion/ - this.floatState = this.states.discrete.FloatingCondition.state; - this.speedState = this.states.discrete.Speed.state; - this.headingState = this.states.discrete.Heading.state; - this.wavCre = wavCre; - this.position = position; // measured position in % of LOA - this.g = g; - this.rho = rho; - this.critical_damping_percentage = critDampPercentage; // this parameter is used to take into account the water viscosity - this.delta = this.ship.structure.hull.attributes.prismaticLengthRatio; // length ratio of two prismatic bodies that represent the ship - this.output = [ "verticalMotion" ]; +function WaveMotion( ship, states, wavCre, position = 0, critDampPercentage = 20, g = 9.81, rho = 1025 ) { - this.cacheDependence = [ "FloatingCondition", "Speed", "Heading" ]; - this.cache = {}; + StateModule.call( this, ship, states ); + if ( typeof this.states.discrete.FloatingCondition === "undefined" ) { - Object.defineProperties( this, { - coefficients: StateModule.prototype.memoized( function () { + this.setDraft(); - var bethaDeg = Math.abs( this.wavCre.waveDef.heading - this.headingState.heading ); - var betha = bethaDeg * Math.PI / 180; - var speedSI = 0.514444 * this.speedState.speed; - var Froude_N = speedSI / Math.sqrt( this.g * this.floatState.LWL ); - //var wave_period = 2*Math.PI/this.wavCre.waveDef.waveFreq; - var wave_number = Math.pow( this.wavCre.waveDef.waveFreq, 2 ) / this.g; - var eff_wave_number = Math.abs( wave_number * Math.cos( betha ) ); - var smith_factor = Math.exp( - wave_number * this.floatState.T ); - var alpha = 1 - Froude_N * Math.sqrt( wave_number * this.floatState.LWL ) * Math.cos( betha ); - var encounter_frequency = this.wavCre.waveDef.waveFreq * alpha; + } - return { betha, Froude_N, wave_number, eff_wave_number, smith_factor, alpha, encounter_frequency }; + if ( typeof this.states.discrete.Speed === "undefined" ) { // if vessel does not have a speed state - }, "coefficients" ), - verticalMotion: StateModule.prototype.memoized( function () { + this.setSpeed(); // use its design speed - var Breadth = this.floatState.BWL * this.floatState.Cb; - var cgDistance = this.position / 100 * this.ship.structure.hull.attributes.LOA - this.states.discrete.FloatingCondition.state.w.cg.x; - var sectional_hydro_damping = 2 * Math.sin( 0.5 * this.coefficients.wave_number * Breadth * Math.pow( this.coefficients.alpha, 2 ) ) * Math.exp( - this.coefficients.wave_number * - this.floatState.T * Math.pow( this.coefficients.alpha, 2 ) ); + } - var a, b; - a = Math.pow( 1 - this.coefficients.wave_number * this.floatState.T, 2 ); - b = Math.pow( ( Math.pow( sectional_hydro_damping, 2 ) / ( this.coefficients.wave_number * Breadth * Math.pow( this.coefficients.alpha, 3 ) ) ), 2 ); - var f = Math.sqrt( a + b ); - var eta = 1 / ( Math.sqrt( Math.pow( ( 1 - 2 * this.coefficients.wave_number * this.floatState.T * Math.pow( this.coefficients.alpha, 2 ) ), 2 ) + Math.pow( Math.pow( sectional_hydro_damping, 2 ) / - ( this.coefficients.wave_number * Breadth * Math.pow( this.coefficients.alpha, 2 ) ), 2 ) ) ); + if ( typeof this.states.discrete.Heading === "undefined" ) { - var F = this.coefficients.smith_factor * f * ( 2 / ( this.coefficients.eff_wave_number * this.floatState.LWL ) ) * Math.sin( this.coefficients.eff_wave_number * this.floatState.LWL / 2 ); - var FRF_Heave = this.wavCre.waveDef.waveAmplitude * eta * F; + this.setHeading(); - var G = this.coefficients.smith_factor * f * ( 24 / ( Math.pow( this.coefficients.eff_wave_number * this.floatState.LWL, 2 ) * this.floatState.LWL ) ) * ( Math.sin( this.coefficients.eff_wave_number * - this.floatState.LWL / 2 ) - ( this.coefficients.eff_wave_number * this.floatState.LWL / 2 ) * Math.cos( this.coefficients.eff_wave_number * this.floatState.LWL / 2 ) ); - var FRF_Pitch = this.wavCre.waveDef.waveAmplitude * eta * G; + } - var Pitch_Movement = Math.abs( FRF_Pitch * cgDistance ); - var Pitch_Acceleration = Math.pow( this.coefficients.encounter_frequency, 2 ) * Pitch_Movement; + this.floatState = this.states.discrete.FloatingCondition.state; + this.speedState = this.states.discrete.Speed.state; + this.headingState = this.states.discrete.Heading.state; + this.wavCre = wavCre; + this.position = position; // measured position in % of LOA + this.g = g; + this.rho = rho; + this.critical_damping_percentage = critDampPercentage; // this parameter is used to take into account the water viscosity + this.delta = this.ship.structure.hull.attributes.prismaticLengthRatio; // length ratio of two prismatic bodies that represent the ship + this.output = [ "verticalMotion" ]; - var Heave_Amplitude = Math.abs( FRF_Heave ); - var Heave_Acceleration = Math.pow( this.coefficients.encounter_frequency, 2 ) * Math.abs( FRF_Heave ); + this.cacheDependence = [ "FloatingCondition", "Speed", "Heading" ]; + this.cache = {}; - var Vertical_Movement = Math.sqrt( Math.pow( Heave_Amplitude, 2 ) + Math.pow( Pitch_Movement, 2 ) ); - var Vertical_Acceleration = Math.pow( this.coefficients.encounter_frequency, 2 ) * Vertical_Movement; +} - return { - pitchAmp: FRF_Pitch, pitchMov: Pitch_Movement, pitchAcc: Pitch_Acceleration, heaveAmp: Heave_Amplitude, heaveAcc: Heave_Acceleration, - verticalMov: Vertical_Movement, verticalAcc: Vertical_Acceleration - }; +WaveMotion.prototype = Object.create( StateModule.prototype ); - }, "verticalMotion" ), - bendingMoment: StateModule.prototype.memoized( function () { +Object.assign( WaveMotion.prototype, { + constructor: WaveMotion +} ); - var Cb_mom = Math.max( 0.6, this.floatState.Cb ); - var phi = 2.5 * ( 1 - Cb_mom ); - var F_Cb = Math.pow( 1 - phi, 2 ) + 0.6 * this.coefficients.alpha * ( 2 - phi ); - var F_v = 1 + 3 * Math.pow( this.coefficients.Froude_N, 2 ); - return this.wavCre.waveDef.waveAmplitude * ( this.coefficients.smith_factor * ( ( 1 - this.coefficients.wave_number * this.floatState.T ) / ( Math.pow( this.floatState.LWL * this.coefficients.eff_wave_number, 2 ) ) ) * - ( 1 - Math.cos( this.coefficients.eff_wave_number * this.floatState.LWL / 2 ) - ( this.coefficients.eff_wave_number * this.floatState.LWL / 4 ) * Math.sin( this.coefficients.eff_wave_number * this.floatState.LWL / 2 ) ) * - F_v * F_Cb * Math.pow( Math.abs( Math.cos( this.coefficients.betha ) ), 1 / 3 ) ) * this.rho * this.g * this.floatState.BWL * Math.pow( this.floatState.LWL, 2 ) / 1000000; +Object.defineProperties( WaveMotion.prototype, { + coefficients: StateModule.prototype.memoized( function () { - }, "bendingMoment" ), - rollAmp: StateModule.prototype.memoized( function () { + var bethaDeg = Math.abs( this.wavCre.waveDef.heading - this.headingState.heading ); + var betha = bethaDeg * Math.PI / 180; + var speedSI = 0.514444 * this.speedState.speed; + var Froude_N = speedSI / Math.sqrt( this.g * this.floatState.LWL ); + //var wave_period = 2*Math.PI/this.wavCre.waveDef.waveFreq; + var wave_number = Math.pow( this.wavCre.waveDef.waveFreq, 2 ) / this.g; + var eff_wave_number = Math.abs( wave_number * Math.cos( betha ) ); + var smith_factor = Math.exp( - wave_number * this.floatState.T ); + var alpha = 1 - Froude_N * Math.sqrt( wave_number * this.floatState.LWL ) * Math.cos( betha ); + var encounter_frequency = this.wavCre.waveDef.waveFreq * alpha; - // estimate natural roll period - var naturalPeriod = ( 2 * this.floatState.BWL * Math.PI * ( 0.35 + 0.45 ) / 2 ) / Math.pow( this.g * this.floatState.GMt, 0.5 ); + return { betha, Froude_N, wave_number, eff_wave_number, smith_factor, alpha, encounter_frequency }; - var breadth_ratio = ( this.floatState.Cwp - this.delta ) / ( 1 - this.delta ); - var A_0 = this.floatState.Cb * this.floatState.BWL * this.floatState.T / ( this.delta + breadth_ratio * ( 1 - this.delta ) ); + }, "coefficients" ), + verticalMotion: StateModule.prototype.memoized( function () { - var Breadth_draft_ratio0 = this.floatState.BWL / this.floatState.T; - var a0, b0, d0; - if ( ( 3 <= Breadth_draft_ratio0 ) && ( Breadth_draft_ratio0 <= 6 ) ) { + var Breadth = this.floatState.BWL * this.floatState.Cb; + var cgDistance = this.position / 100 * this.ship.structure.hull.attributes.LOA - this.states.discrete.FloatingCondition.state.w.cg.x; + var sectional_hydro_damping = 2 * Math.sin( 0.5 * this.coefficients.wave_number * Breadth * Math.pow( this.coefficients.alpha, 2 ) ) * Math.exp( - this.coefficients.wave_number * + this.floatState.T * Math.pow( this.coefficients.alpha, 2 ) ); - a0 = 0.256 * Breadth_draft_ratio0 - 0.286; - b0 = - 0.11 * Breadth_draft_ratio0 - 2.55; - d0 = 0.033 * Breadth_draft_ratio0 - 1.419; + var a, b; + a = Math.pow( 1 - this.coefficients.wave_number * this.floatState.T, 2 ); + b = Math.pow( ( Math.pow( sectional_hydro_damping, 2 ) / ( this.coefficients.wave_number * Breadth * Math.pow( this.coefficients.alpha, 3 ) ) ), 2 ); + var f = Math.sqrt( a + b ); + var eta = 1 / ( Math.sqrt( Math.pow( ( 1 - 2 * this.coefficients.wave_number * this.floatState.T * Math.pow( this.coefficients.alpha, 2 ) ), 2 ) + Math.pow( Math.pow( sectional_hydro_damping, 2 ) / + ( this.coefficients.wave_number * Breadth * Math.pow( this.coefficients.alpha, 2 ) ), 2 ) ) ); - } else if ( ( 1 <= Breadth_draft_ratio0 ) && ( Breadth_draft_ratio0 < 3 ) ) { + var F = this.coefficients.smith_factor * f * ( 2 / ( this.coefficients.eff_wave_number * this.floatState.LWL ) ) * Math.sin( this.coefficients.eff_wave_number * this.floatState.LWL / 2 ); + var FRF_Heave = this.wavCre.waveDef.waveAmplitude * eta * F; - a0 = - 3.94 * Breadth_draft_ratio0 + 13.69; - b0 = - 2.12 * Breadth_draft_ratio0 - 1.89; - d0 = 1.16 * Breadth_draft_ratio0 - 7.97; + var G = this.coefficients.smith_factor * f * ( 24 / ( Math.pow( this.coefficients.eff_wave_number * this.floatState.LWL, 2 ) * this.floatState.LWL ) ) * ( Math.sin( this.coefficients.eff_wave_number * + this.floatState.LWL / 2 ) - ( this.coefficients.eff_wave_number * this.floatState.LWL / 2 ) * Math.cos( this.coefficients.eff_wave_number * this.floatState.LWL / 2 ) ); + var FRF_Pitch = this.wavCre.waveDef.waveAmplitude * eta * G; - } else { + var Pitch_Movement = Math.abs( FRF_Pitch * cgDistance ); + var Pitch_Acceleration = Math.pow( this.coefficients.encounter_frequency, 2 ) * Pitch_Movement; - console.error( "The B/T relation is not being respected for the roll formula. It should be 1 <= B/T < 6, not" + " " + ( this.floatState.BWL / this.floatState.T ).toFixed( 2 ) + "." ); + var Heave_Amplitude = Math.abs( FRF_Heave ); + var Heave_Acceleration = Math.pow( this.coefficients.encounter_frequency, 2 ) * Math.abs( FRF_Heave ); - } + var Vertical_Movement = Math.sqrt( Math.pow( Heave_Amplitude, 2 ) + Math.pow( Pitch_Movement, 2 ) ); + var Vertical_Acceleration = Math.pow( this.coefficients.encounter_frequency, 2 ) * Vertical_Movement; - var b_44_0 = this.rho * A_0 * Math.pow( this.floatState.BWL, 2 ) * a0 * Math.exp( b0 * Math.pow( this.coefficients.encounter_frequency, - 1.3 ) ) * Math.pow( this.coefficients.encounter_frequency, d0 ) / - ( Math.sqrt( this.floatState.BWL / ( 2 * this.g ) ) ); + return { + pitchAmp: FRF_Pitch, pitchMov: Pitch_Movement, pitchAcc: Pitch_Acceleration, heaveAmp: Heave_Amplitude, heaveAcc: Heave_Acceleration, + verticalMov: Vertical_Movement, verticalAcc: Vertical_Acceleration + }; - var A_1 = breadth_ratio * A_0; - var B_1 = breadth_ratio * this.floatState.BWL; - var Breadth_draft_ratio1 = B_1 / this.floatState.T; - var a1, b1, d1; - if ( ( 3 <= Breadth_draft_ratio1 ) && ( Breadth_draft_ratio1 <= 6 ) ) { + }, "verticalMotion" ), + bendingMoment: StateModule.prototype.memoized( function () { - a1 = 0.256 * Breadth_draft_ratio1 - 0.286; - b1 = - 0.11 * Breadth_draft_ratio1 - 2.55; - d1 = 0.033 * Breadth_draft_ratio1 - 1.419; + var Cb_mom = Math.max( 0.6, this.floatState.Cb ); + var phi = 2.5 * ( 1 - Cb_mom ); + var F_Cb = Math.pow( 1 - phi, 2 ) + 0.6 * this.coefficients.alpha * ( 2 - phi ); + var F_v = 1 + 3 * Math.pow( this.coefficients.Froude_N, 2 ); + return this.wavCre.waveDef.waveAmplitude * ( this.coefficients.smith_factor * ( ( 1 - this.coefficients.wave_number * this.floatState.T ) / ( Math.pow( this.floatState.LWL * this.coefficients.eff_wave_number, 2 ) ) ) * + ( 1 - Math.cos( this.coefficients.eff_wave_number * this.floatState.LWL / 2 ) - ( this.coefficients.eff_wave_number * this.floatState.LWL / 4 ) * Math.sin( this.coefficients.eff_wave_number * this.floatState.LWL / 2 ) ) * + F_v * F_Cb * Math.pow( Math.abs( Math.cos( this.coefficients.betha ) ), 1 / 3 ) ) * this.rho * this.g * this.floatState.BWL * Math.pow( this.floatState.LWL, 2 ) / 1000000; - } else if ( ( 1 <= Breadth_draft_ratio1 ) && ( Breadth_draft_ratio1 < 3 ) ) { + }, "bendingMoment" ), + rollAmp: StateModule.prototype.memoized( function () { - a1 = - 3.94 * Breadth_draft_ratio1 + 13.69; - b1 = - 2.12 * Breadth_draft_ratio1 - 1.89; - d1 = 1.16 * Breadth_draft_ratio1 - 7.97; + // estimate natural roll period + var naturalPeriod = ( 2 * this.floatState.BWL * Math.PI * ( 0.35 + 0.45 ) / 2 ) / Math.pow( this.g * this.floatState.GMt, 0.5 ); - } else { + var breadth_ratio = ( this.floatState.Cwp - this.delta ) / ( 1 - this.delta ); + var A_0 = this.floatState.Cb * this.floatState.BWL * this.floatState.T / ( this.delta + breadth_ratio * ( 1 - this.delta ) ); - console.error( "The vessel dimensions are out of range for the roll formula." ); + var Breadth_draft_ratio0 = this.floatState.BWL / this.floatState.T; + var a0, b0, d0; + if ( ( 3 <= Breadth_draft_ratio0 ) && ( Breadth_draft_ratio0 <= 6 ) ) { - } + a0 = 0.256 * Breadth_draft_ratio0 - 0.286; + b0 = - 0.11 * Breadth_draft_ratio0 - 2.55; + d0 = 0.033 * Breadth_draft_ratio0 - 1.419; - var b_44_1 = this.rho * A_1 * Math.pow( B_1, 2 ) * a1 * Math.exp( b1 * Math.pow( this.coefficients.encounter_frequency, - 1.3 ) ) * Math.pow( this.coefficients.encounter_frequency, d1 ) / - ( Math.sqrt( B_1 / ( 2 * this.g ) ) ); + } else if ( ( 1 <= Breadth_draft_ratio0 ) && ( Breadth_draft_ratio0 < 3 ) ) { - var b_44 = this.floatState.LWL * b_44_0 * ( this.delta + b_44_1 * ( 1 - this.delta ) / b_44_0 ); - var critical_damping_frac = this.critical_damping_percentage / 100; - var restoring_moment_coeff = this.g * this.rho * this.floatState.Cb * this.floatState.LWL * this.floatState.BWL * this.floatState.T * this.floatState.GMt; - var add_damping = restoring_moment_coeff * naturalPeriod / Math.PI; + a0 = - 3.94 * Breadth_draft_ratio0 + 13.69; + b0 = - 2.12 * Breadth_draft_ratio0 - 1.89; + d0 = 1.16 * Breadth_draft_ratio0 - 7.97; - var damping_ratio = Math.sqrt( b_44_1 / b_44_0 ); - var roll_hydro_damping = b_44 + add_damping * critical_damping_frac; + } else { - var excitation_frequency, A, B, C, D; + console.error( "The B/T relation is not being respected for the roll formula. It should be 1 <= B/T < 6, not" + " " + ( this.floatState.BWL / this.floatState.T ).toFixed( 2 ) + "." ); - if ( this.wavCre.waveDef.heading == 90 || this.wavCre.waveDef.heading == 270 ) { + } - excitation_frequency = Math.sqrt( this.rho * Math.pow( this.g, 2 ) * b_44_0 / this.coefficients.encounter_frequency ) * ( this.delta + damping_ratio * - ( 1 - this.delta ) ) * this.floatState.LWL; + var b_44_0 = this.rho * A_0 * Math.pow( this.floatState.BWL, 2 ) * a0 * Math.exp( b0 * Math.pow( this.coefficients.encounter_frequency, - 1.3 ) ) * Math.pow( this.coefficients.encounter_frequency, d0 ) / + ( Math.sqrt( this.floatState.BWL / ( 2 * this.g ) ) ); - } else { + var A_1 = breadth_ratio * A_0; + var B_1 = breadth_ratio * this.floatState.BWL; + var Breadth_draft_ratio1 = B_1 / this.floatState.T; + var a1, b1, d1; + if ( ( 3 <= Breadth_draft_ratio1 ) && ( Breadth_draft_ratio1 <= 6 ) ) { - A = Math.abs( Math.sin( this.coefficients.betha ) ) * Math.sqrt( this.rho * Math.pow( this.g, 2 ) / this.coefficients.encounter_frequency ) * Math.sqrt( b_44_0 ) * 2 / this.coefficients.eff_wave_number; - B = Math.pow( Math.sin( 0.5 * this.delta * this.floatState.LWL * this.coefficients.eff_wave_number ), 2 ); - C = Math.pow( damping_ratio * Math.sin( 0.5 * ( 1 - this.delta ) * this.floatState.LWL * this.coefficients.eff_wave_number ), 2 ); - D = 2 * damping_ratio * Math.sin( 0.5 * this.delta * this.floatState.LWL * this.coefficients.eff_wave_number ) * Math.sin( 0.5 * ( 1 - this.delta ) * - this.floatState.LWL * this.coefficients.eff_wave_number ) * Math.cos( 0.5 * this.floatState.LWL * this.coefficients.eff_wave_number ); - excitation_frequency = A * Math.sqrt( B + C + D ); + a1 = 0.256 * Breadth_draft_ratio1 - 0.286; + b1 = - 0.11 * Breadth_draft_ratio1 - 2.55; + d1 = 0.033 * Breadth_draft_ratio1 - 1.419; - } + } else if ( ( 1 <= Breadth_draft_ratio1 ) && ( Breadth_draft_ratio1 < 3 ) ) { - A = Math.pow( - Math.pow( this.coefficients.encounter_frequency * naturalPeriod / ( 2 * Math.PI ), 2 ) + 1, 2 ); - B = Math.pow( restoring_moment_coeff, 2 ); - C = Math.pow( this.coefficients.encounter_frequency * roll_hydro_damping, 2 ); + a1 = - 3.94 * Breadth_draft_ratio1 + 13.69; + b1 = - 2.12 * Breadth_draft_ratio1 - 1.89; + d1 = 1.16 * Breadth_draft_ratio1 - 7.97; - return this.wavCre.waveDef.waveAmplitude * excitation_frequency / ( Math.sqrt( A * B + C ) ); + } else { - }, "rollAmp" ) - } ); + console.error( "The vessel dimensions are out of range for the roll formula." ); - } + } -} + var b_44_1 = this.rho * A_1 * Math.pow( B_1, 2 ) * a1 * Math.exp( b1 * Math.pow( this.coefficients.encounter_frequency, - 1.3 ) ) * Math.pow( this.coefficients.encounter_frequency, d1 ) / + ( Math.sqrt( B_1 / ( 2 * this.g ) ) ); -// partially adapted from http://www.shiplab.ntnu.co/app/holtrop/ + var b_44 = this.floatState.LWL * b_44_0 * ( this.delta + b_44_1 * ( 1 - this.delta ) / b_44_0 ); + var critical_damping_frac = this.critical_damping_percentage / 100; + var restoring_moment_coeff = this.g * this.rho * this.floatState.Cb * this.floatState.LWL * this.floatState.BWL * this.floatState.T * this.floatState.GMt; + var add_damping = restoring_moment_coeff * naturalPeriod / Math.PI; + var damping_ratio = Math.sqrt( b_44_1 / b_44_0 ); + var roll_hydro_damping = b_44 + add_damping * critical_damping_frac; -class HullResistance extends StateModule { + var excitation_frequency, A, B, C, D; - constructor( ship, states, propeller, wavCre, g = 9.81, rho = 1025, mi = 0.00122 ) { + if ( this.wavCre.waveDef.heading == 90 || this.wavCre.waveDef.heading == 270 ) { - super( ship, states ); - this.propeller = propeller; + excitation_frequency = Math.sqrt( this.rho * Math.pow( this.g, 2 ) * b_44_0 / this.coefficients.encounter_frequency ) * ( this.delta + damping_ratio * + ( 1 - this.delta ) ) * this.floatState.LWL; - if ( typeof this.states.discrete.FloatingCondition === "undefined" ) { + } else { - this.setDraft(); + A = Math.abs( Math.sin( this.coefficients.betha ) ) * Math.sqrt( this.rho * Math.pow( this.g, 2 ) / this.coefficients.encounter_frequency ) * Math.sqrt( b_44_0 ) * 2 / this.coefficients.eff_wave_number; + B = Math.pow( Math.sin( 0.5 * this.delta * this.floatState.LWL * this.coefficients.eff_wave_number ), 2 ); + C = Math.pow( damping_ratio * Math.sin( 0.5 * ( 1 - this.delta ) * this.floatState.LWL * this.coefficients.eff_wave_number ), 2 ); + D = 2 * damping_ratio * Math.sin( 0.5 * this.delta * this.floatState.LWL * this.coefficients.eff_wave_number ) * Math.sin( 0.5 * ( 1 - this.delta ) * + this.floatState.LWL * this.coefficients.eff_wave_number ) * Math.cos( 0.5 * this.floatState.LWL * this.coefficients.eff_wave_number ); + excitation_frequency = A * Math.sqrt( B + C + D ); } - if ( typeof this.states.discrete.Speed === "undefined" ) { // if vessel does not have a speed state + A = Math.pow( - Math.pow( this.coefficients.encounter_frequency * naturalPeriod / ( 2 * Math.PI ), 2 ) + 1, 2 ); + B = Math.pow( restoring_moment_coeff, 2 ); + C = Math.pow( this.coefficients.encounter_frequency * roll_hydro_damping, 2 ); - this.setSpeed(); // use its design speed + return this.wavCre.waveDef.waveAmplitude * excitation_frequency / ( Math.sqrt( A * B + C ) ); - } + }, "rollAmp" ) +} ); +function Positioning( ship, states, path ) { - this.speedState = this.states.discrete.Speed.state; - this.floatState = this.states.discrete.FloatingCondition.state; - this.wavCre = wavCre; - this.g = g; - this.rho = rho; - this.mi = mi; // dynamic viscosity - this.b = this.ship.structure.hull.attributes.bulb; // has a bulb? Boolean. - this.tr = this.ship.structure.hull.attributes.transom; // transom. Boolean. - this.cstern = this.ship.structure.hull.attributes.cstern; // afterbody form. - // Pram with Gondola = -25, V-Shaped Sections = -10, Normal Section Shape = 0, U-Shaped Sections with Hognes Stern = 10 - this.appendices = this.ship.structure.hull.attributes.appendices; // appendices information - // skegRudder has coeff from 1.5 to 2.0 - // sternRudder has coeff from 1.3 to 1.5 - // twinScrewBalanceRudder has coeff 2.8 - // shaftBrackets has coeff 3.0 - // skeg has coeff from 1.5 to 2.0 - // strutBossings has coeff 3.0 - // hullBossings has coeff 2.0 - // shafts have coeff from 2 to 4 - // stabilizerFins has coeff 2.8 - // dome has coeff 2.7 - // bilgeKeel has coeff 1.4 - this.output = [ "totalResistance", "efficiency" ]; + this.ship = ship; + this.states = states; - this.cacheDependence = [ "FloatingCondition", "Speed" ]; - this.cache = {}; + this.routeData = {}; + this.routeData.pathVec = []; + this.routeData.unitPath = []; + this.routeData.heading = []; + this.routeData.totalDist = 0; + this.routeData.legDistance = []; + for ( var leg = 0; leg < path.length - 1; leg ++ ) { - Object.defineProperties( this, { - coefficients: StateModule.prototype.memoized( function () { + this.routeData.pathVec[ leg ] = path[ leg + 1 ].map( ( num, idx ) => num - path[ leg ][ idx ] ); + this.routeData.legDistance[ leg ] = Math.sqrt( this.routeData.pathVec[ leg ].map( ( num ) => Math.pow( num, 2 ) ).reduce( ( num1, num2 ) => num1 + num2 ) ); + this.routeData.unitPath[ leg ] = this.routeData.pathVec[ leg ].map( ( num ) => num / this.routeData.legDistance[ leg ] ); - let lcb = 100 * ( this.floatState.LCB - ( this.floatState.minXs + this.floatState.LWL / 2 ) ) / this.floatState.LWL; // % + // get heading in relation to North/y axis + var heading = Math.acos( this.routeData.pathVec[ leg ][ 1 ] / this.routeData.legDistance[ leg ] ); + heading *= 180 / Math.PI; // convert to degrees - let Tfore; - if ( this.floatState.draftfp === null ) { + if ( this.routeData.pathVec[ leg ][ 0 ] < 0 ) { - Tfore = this.floatState.T; + heading = 360 - heading; - } else { + } - Tfore = this.floatState.draftfp; + this.routeData.heading[ leg ] = heading; - } + this.routeData.totalDist += this.routeData.legDistance[ leg ]; - let Taft; - if ( this.floatState.draftap === null ) { + } - Taft = this.floatState.T; + // initialize vessel on path + this.states.continuous.Positioning = {}; + this.states.continuous.Positioning.position = path[ 0 ]; + this.states.continuous.Positioning.travelLegDist = 0; + this.states.continuous.Positioning.travelDist = 0; + this.positionState = this.states.continuous.Positioning; - } else { + this.states.discrete.Leg = { + state: { + leg: 0 + }, + thisStateVer: 1 + }; + this.legState = this.states.discrete.Leg.state; - Taft = this.floatState.draftap; + if ( typeof this.states.discrete.Speed === "undefined" ) { // if vessel does not have a speed state - } + StateModule.prototype.setSpeed.call( this ); // use its design speed - let T = ( Tfore + Taft ) / 2; // m, average draft - let hb = Tfore / 2; - let abt = Math.PI * Math.pow( Tfore / 2, 2 ) * this.b / 7.7; // transverse sectional bulb area - let c3 = 0.56 * ( Math.pow( abt, 1.5 ) ) / ( this.floatState.BWL * T * ( 0.31 * Math.pow( abt, 0.5 ) + Tfore - hb ) ); - let c2 = Math.exp( - 1.89 * Math.pow( c3, 0.5 ) ); + } - let c4; - if ( Tfore / this.floatState.LWL > 0.04 ) { + if ( typeof this.states.discrete.Heading === "undefined" ) { - c4 = 0.04; + StateModule.prototype.setHeading.call( this, this.routeData.heading[ 0 ] ); - } else { + } - c4 = Tfore / this.floatState.LWL; + this.advanceShip = function ( timeStep ) { // calculate advanced distance during one time step - } + var remVec, remDist; + var advDist = timeStep * 1 / 3600 * this.states.discrete.Speed.state.speed; - // correlation allowance coefficient - let ca = 0.006 * Math.pow( this.floatState.LWL + 100, - 0.16 ) - 0.00205 + 0.003 * Math.pow( this.floatState.LWL / 7.5, 0.5 ) * Math.pow( this.floatState.Cb, 4 ) * c2 * ( 0.04 - c4 ); - let wa = this.floatState.LWL * ( 2 * T + this.floatState.BWL ) * Math.pow( this.floatState.Cm, 0.5 ) * ( 0.453 + 0.4425 * this.floatState.Cb - 0.2862 * this.floatState.Cm - 0.003467 * - this.floatState.BWL / T + 0.3696 * this.floatState.Cwp ) + 2.38 * abt / this.floatState.Cb; // wetted area + while ( 0 < advDist ) { - let lr = this.floatState.LWL * ( 1 - this.floatState.Cp + ( 0.06 * this.floatState.Cp * ( lcb / 100 ) / ( 4 * this.floatState.Cp - 1 ) ) ); - let c14 = 1 + 0.011 * this.cstern; - let k = 0.93 + ( 0.487118 * c14 * Math.pow( this.floatState.BWL / this.floatState.LWL, 1.06806 ) * Math.pow( T / this.floatState.LWL, 0.46106 ) * Math.pow( this.floatState.LWL / - lr, 0.121563 ) * Math.pow( Math.pow( this.floatState.LWL, 3 ) / this.floatState.Vs, 0.36486 ) * Math.pow( 1 - this.floatState.Cp, - 0.604247 ) ); // form factor + remVec = path[ this.legState.leg + 1 ].map( ( num, idx ) => num - this.positionState.position[ idx ] ); + remDist = Math.sqrt( remVec.map( ( num ) => Math.pow( num, 2 ) ).reduce( ( num1, num2 ) => num1 + num2 ) ); + if ( advDist <= remDist ) { - let speedSI = 0.514444 * this.speedState.speed; // convert the speed from knots to m/s - let re = this.rho * this.floatState.LWL * speedSI / this.mi; // Reynolds number - let cf = 0.075 / Math.pow( ( Math.log( re ) / Math.log( 10 ) ) - 2, 2 ); // frictional coefficient + this.positionState.position = this.positionState.position.map( ( num, idx ) => num + advDist * this.routeData.unitPath[ this.legState.leg ][ idx ] ); - return { lcb, Tfore, Taft, T, hb, c2, ca, abt, wa, lr, k, speedSI, cf }; + // add to traveled distance + this.positionState.travelLegDist = this.positionState.travelLegDist + advDist; + this.positionState.travelDist = this.positionState.travelDist + advDist; - }, "coefficients" ), - calmResistance: StateModule.prototype.memoized( function () { // N, total hull resistance in calm waters + advDist = 0; - let at = 0.95 * ( this.coefficients.Taft - this.coefficients.Taft * 0.9225 ) * this.floatState.BWL * 0.89 * this.tr; // transom stern area - let rf = 0.5 * this.rho * Math.pow( this.coefficients.speedSI, 2 ) * this.coefficients.wa * this.coefficients.cf; // frictional resistance + } else if ( path[ this.legState.leg + 2 ] !== undefined ) { // trip has another leg - let fnt; - if ( at === 0 ) { + // change direction and continue sailing + advDist -= remDist; + this.positionState.position = path[ this.legState.leg + 1 ]; - fnt = 0; + // add to traveled distance + this.positionState.travelLegDist = 0; + this.positionState.travelDist = this.positionState.travelDist + remDist; - } else { + this.legState.leg ++; + this.states.discrete.Leg.thisStateVer ++; - fnt = this.coefficients.speedSI / ( Math.pow( 2 * this.g * at / ( this.floatState.BWL + this.floatState.BWL * this.floatState.Cwp ), 0.5 ) ); + StateModule.prototype.setHeading.call( this, this.routeData.heading[ this.legState.leg ] ); + console.log( "Vessel reached pivot point in navigation and entered leg " + this.legState.leg + "." ); - } + } else { - let c6; - if ( fnt < 5 ) { + this.positionState.position = this.positionState.position.map( ( num, idx ) => num + advDist * this.routeData.unitPath[ this.legState.leg ][ idx ] ); - c6 = 0.2 * ( 1 - 0.2 * fnt ); + // add to traveled distance + this.positionState.travelLegDist = this.positionState.travelLegDist + advDist; + this.positionState.travelDist = this.positionState.travelDist + advDist; - } else { + console.log( "Vessel reached final destination." ); + break; - c6 = 0; + } - } + } - let rtr = 0.5 * this.rho * Math.pow( this.coefficients.speedSI, 2 ) * at * c6; // stern resistance - let sapp = 0; - let mult = 0; - for ( let prop in this.appendices ) { + }; - sapp += this.appendices[ prop ].area; - mult += this.appendices[ prop ].coeff * this.appendices[ prop ].area; +} +// @ferrari212 +function Manoeuvring( ship, states, hullResistance, propellerInteraction, fuelConsumption, manoeuvring, rho = 1025 ) { - } + StateModule.call( this, ship, states ); + if ( typeof this.states.discrete.FloatingCondition === "undefined" ) { - let k2; - if ( sapp !== 0 ) { + this.setDraft(); - k2 = mult / sapp; + } - } else { + if ( typeof this.states.discrete.Speed === "undefined" ) { // if vessel does not have a speed state - k2 = 0; + this.setSpeed(); // use its design speed - } + } - let rapp = 0.5 * this.rho * Math.pow( this.coefficients.speedSI, 2 ) * sapp * k2 * this.coefficients.cf; // appendage resistance - let fn = this.coefficients.speedSI / Math.pow( this.g * this.floatState.LWL, 0.5 ); //Froude number + this.hullRes = hullResistance; + this.propellerInteraction = propellerInteraction; + this.fuelConsumption = fuelConsumption; + this.powerPlant = fuelConsumption.powerPlant; + this.manoeuvring = manoeuvring; + this.state = {}; + this.rho = propellerInteraction.rho; + this.propeller = this.propellerInteraction.propeller; + this.speedState = this.states.discrete.Speed.state; + this.floatState = this.states.discrete.FloatingCondition.state; + this.resistanceState = this.states.discrete.HullResistance.state; - let c7; - if ( this.floatState.BWL / this.floatState.LWL < 0.11 ) { + const YAW = manoeuvring.initial_yaw || 0; + const AN = manoeuvring.initial_angle || 0; + // The modules bellow use the value in knots for the ship speed, + // for the maneuvering model it is going to be used the values in SI (m/s). @ferrari212 + Object.assign( this.states, { + DX: { x: 0, y: 0, yaw: 0 }, + V: { u: 0, v: 0, yaw_dot: 0 }, + n: 0, + yaw: YAW, + rudderAngle: AN, + load: 0 + } ); - c7 = 0.229577 * Math.pow( this.floatState.BWL / this.floatState.LWL, 0.33333 ); + var engines = this.powerPlant.main.engines; + this.powerPlant.engCapac = []; + var engCapac = this.powerPlant.engCapac; - } else if ( this.floatState.BWL / this.floatState.LWL < 0.25 ) { + for ( var i = 0; i < engines.length; i ++ ) { - c7 = this.floatState.BWL / this.floatState.LWL; + engCapac[ i ] = engines[ i ].MCR; - } else { + } - c7 = 0.5 - 0.0625 * this.floatState.LWL / this.floatState.BWL; + this.totalCapac = engCapac.reduce( ( a, b ) => a + b, 0 ); - } + // The function simplify the resitence curve by Rt = k*u^2 + // the interpolation could be improved by other types of functions interporlation @ferrari212 + function intResist( man ) { - // calculate the half angle of entrance - let ie = 1 + 89 * Math.exp( - Math.pow( this.floatState.LWL / this.floatState.BWL, 0.80856 ) * Math.pow( 1 - this.floatState.Cwp, 0.30484 ) * Math.pow( 1 - this.floatState.Cp - 0.0225 * - ( this.coefficients.lcb / 100 ), 0.6367 ) * Math.pow( this.coefficients.lr / this.floatState.BWL, 0.34574 ) * Math.pow( 100 * ( this.floatState.Vs / Math.pow( this.floatState.LWL, 3 ) ), 0.16302 ) ); - let c1 = 2223105 * Math.pow( c7, 3.78613 ) * Math.pow( this.coefficients.T / this.floatState.BWL, 1.07961 ) * Math.pow( 90 - ie, - 1.37565 ); - let c5 = 1 - ( 0.8 * at ) / ( this.floatState.BWL * this.coefficients.T * this.floatState.Cm ); + var pow = Math.pow; - let c15; - if ( Math.pow( this.floatState.LWL, 3 ) / this.floatState.Vs < 512 ) { + const U = ( Boolean( man.states.calculationParameters.speed ) ) ? man.states.calculationParameters.speed : 10; - c15 = - 1.69385; + man.hullRes.setSpeed( U ); + const CONV = 0.5144447; + const R = man.hullRes.totalResistance.Rtadd; - } else if ( Math.pow( this.floatState.LWL, 3 ) / this.floatState.Vs < 1726.91 ) { + var k = R / ( Math.pow( U * CONV, 2 ) ); - c15 = - 1.69385 + ( this.floatState.LWL / Math.pow( this.floatState.Vs, 1 / 3 ) - 8 ) / 2.36; - } else { + var getRes = function ( u ) { - c15 = 0; + return k * ( pow( u, 2 ) ) * Math.sign( u ); - } + }; - let c16; - if ( this.floatState.Cp < 0.8 ) { + return getRes; - c16 = 8.07981 * this.floatState.Cp - 13.8673 * Math.pow( this.floatState.Cp, 2 ) + 6.984388 * Math.pow( this.floatState.Cp, 3 ); + } - } else { + this.getRes = intResist( this ); - c16 = 1.73014 - 0.7067 * this.floatState.Cp; + const W = ship.getWeight(); + this.m = manoeuvring.m || W.mass; - } + // The approximaxion is given by the inercia of an Elipsoid in water + var attributes = this.ship.structure.hull.attributes; + var T = this.ship.designState.calculationParameters.Draft_design; + var approxI = manoeuvring.I || Math.PI * rho * attributes.LOA * attributes.BOA * T * ( 4 * Math.pow( T, 2 ) + Math.pow( attributes.BOA, 2 ) ) / 120; - let m1 = 0.0140407 * ( this.floatState.LWL / this.coefficients.T ) - 1.75254 * ( ( Math.pow( this.floatState.Vs, 1 / 3 ) ) / this.floatState.LWL ) - 4.79323 * ( this.floatState.BWL / this.floatState.LWL ) - c16; + this.M_RB = manoeuvring.M || [ + [ this.m, 0, 0 ], + [ 0, this.m, 0 ], + [ 0, 0, approxI ] + ]; - let lambda; - if ( this.floatState.LWL / this.floatState.BWL > 12 ) { + this.output = [ "hydroCoeff", "dn" ]; - lambda = 1.446 * this.floatState.Cp - 0.36; + this.cacheDependence = [ "PropellerInteraction", "FloatingCondition" ]; + this.cache = {}; - } else { +} - lambda = 1.446 * this.floatState.Cp - 0.03 * ( this.floatState.LWL / this.floatState.BWL ); +Manoeuvring.prototype = Object.create( StateModule.prototype ); - } +Object.assign( Manoeuvring.prototype, { + constructor: Manoeuvring, + getPropResult: function ( n ) { - let c17 = 6919.3 * Math.pow( this.floatState.Cm, - 1.3346 ) * Math.pow( this.floatState.Vs / Math.pow( this.floatState.LWL, 3 ), 2.00977 ) * Math.pow( this.floatState.LWL / this.floatState.BWL - 2, 1.40692 ); - let m3 = - 7.2035 * Math.pow( this.floatState.BWL / this.floatState.LWL, 0.326869 ) * Math.pow( this.coefficients.T / this.floatState.BWL, 0.605375 ); - let m4_0_4 = c15 * 0.4 * Math.exp( - 0.034 * Math.pow( 0.4, - 3.29 ) ); - let rwa_0_4 = c1 * this.coefficients.c2 * c5 * this.floatState.Vs * this.rho * this.g * Math.exp( m1 * Math.pow( 0.4, - 0.9 ) + m4_0_4 * Math.cos( lambda * Math.pow( 0.4, - 2 ) ) ); - let m4_0_55 = c15 * 0.4 * Math.exp( - 0.034 * Math.pow( 0.55, - 3.29 ) ); - let rwa_0_55 = c17 * this.coefficients.c2 * c5 * this.floatState.Vs * this.rho * this.g * Math.exp( m3 * Math.pow( 0.55, - 0.9 ) + m4_0_55 * Math.cos( lambda * - Math.pow( 0.55, - 2 ) ) ); + if ( n === 0 ) return { Fp: 0, Pp: 0, cons: 0 }; - let m4, rwa, rwb, rwab; - if ( fn === 0 ) { + var Va = this.propellerInteraction.propulsion.Va; - m4 = 0; - rwa = 0; // wave resistance for Froude < 0.4 - rwb = 0; // wave resistance for Froude > 0.55 - rwab = 0; + var lcb = 100 * ( this.floatState.LCB - ( this.floatState.minXs + this.floatState.LWL / 2 ) ) / this.floatState.LWL; // % + var J = Math.abs( Va / ( n * this.propeller.D ) ); - } else { + var KT = this.propeller.beta1 - this.propeller.beta2 * J; + var KQ = this.propeller.gamma1 - this.propeller.gamma2 * J; - m4 = c15 * 0.4 * Math.exp( - 0.034 * Math.pow( fn, - 3.29 ) ); - rwa = c1 * this.coefficients.c2 * c5 * this.floatState.Vs * this.rho * this.g * Math.exp( m1 * Math.pow( fn, - 0.9 ) + m4 * Math.cos( lambda * Math.pow( fn, - 2 ) ) ); - rwb = c17 * this.coefficients.c2 * c5 * this.floatState.Vs * this.rho * this.g * Math.exp( m3 * Math.pow( fn, - 0.9 ) + m4 * Math.cos( lambda * Math.pow( fn, - 2 ) ) ); - rwab = rwa_0_4 + ( 10 * fn - 4 ) * ( rwa_0_55 - rwa_0_4 ) / 1.5; + var etar; + if ( this.propeller.noProps === 1 ) { - } + etar = 0.9922 - 0.05908 * this.propeller.AeAo + 0.07424 * ( this.floatState.Cp - 0.0225 * lcb ); - let rw; - if ( fn < 0.4 ) { + } else if ( this.propeller.noProps === 2 ) { - rw = rwa; + etar = 0.9737 + 0.111 * ( this.floatState.Cp - 0.0225 * lcb ) - 0.06325 * this.propeller.P / this.propeller.D; - } else if ( fn <= 0.55 ) { + } - rw = rwab; + var T = KT * this.rho * Math.pow( n, 2 ) * Math.pow( this.propeller.D, 4 ); + var Q = KQ * this.rho * Math.pow( n, 2 ) * Math.pow( this.propeller.D, 5 ); + // console.log( `T: ${T}; Q: ${Q}`); - } else { + var Fp = Math.sign( n ) * T * this.propeller.noProps * ( 1 - this.resistanceState.t ); + var Po = 2 * Math.PI * Math.abs( Q * n ) * this.propeller.noProps; + var Pp = Po * etar; - rw = rwb; + var cons = this.getFuelCons( Pp ); - } + return { Fp, Pp, cons }; - let fni = this.coefficients.speedSI / Math.sqrt( this.g * ( this.coefficients.Tfore - this.coefficients.hb - 0.25 * Math.pow( this.coefficients.abt, 0.5 ) ) + ( 0.15 * Math.pow( this.coefficients.speedSI, 2 ) ) ); - let pb = ( 0.56 * Math.pow( this.coefficients.abt, 0.5 ) ) / ( this.coefficients.Tfore - 1.5 * this.coefficients.hb ); + }, + getFuelCons: function ( Pp ) { - let rb; - if ( this.coefficients.abt === 0 ) { + // share load among engines in a system's array + function shareLoad( system, load ) { - rb = 0; + var triggerRatio = 0.8; // define loading rate at which next engine in the power system will be activated for sharing loads + var cons = 0; - } else { + var engCapac = []; + for ( var i = 0; i < system.engines.length; i ++ ) { - rb = ( 0.11 * Math.exp( - 3 * Math.pow( pb, - 2 ) ) * Math.pow( fni, 3 ) * Math.pow( this.coefficients.abt, 1.5 ) * this.rho * this.g ) / ( 1 + Math.pow( fni, 2 ) ); + engCapac[ i ] = system.engines[ i ].MCR; - } + } - let ra = 0.91 * 0.5 * this.rho * Math.pow( this.coefficients.speedSI, 2 ) * this.coefficients.wa * this.coefficients.ca; - if ( ( this.floatState.LWL / this.floatState.BWL <= 3.9 ) || ( this.floatState.LWL / this.floatState.BWL >= 15 ) ) { + if ( typeof system.etag === "number" ) { // diesel electrical system - console.error( "The L/B relation is not being respected. It should be 3.9 < L/B < 15, not" + " " + ( this.floatState.LWL / this.floatState.BWL ).toFixed( 2 ) + "." ); + load = load / ( system.etas * system.etag ); - } + } else { // diesel mechanical system - if ( ( this.floatState.BWL / this.coefficients.T <= 2.1 ) || ( this.floatState.BWL / this.coefficients.T >= 4 ) ) { + load = load / system.etas; // consumption rate in kg/s - console.error( "The B/T relation is not being respected. It should be 2.1 < B/T < 4, not" + " " + ( this.floatState.BWL / this.coefficients.T ).toFixed( 2 ) + "." ); + } - } + // distribute loads among engines + var totalCapac = engCapac.reduce( ( a, b ) => a + b, 0 ); + var partCapac = totalCapac - engCapac[ engCapac.length - 1 ]; + var loads = Array( engCapac.length ); + if ( load <= triggerRatio * partCapac ) { // if not all engines are loaded above trigger ratio, load them according to trigger rate ceil - if ( ( this.floatState.Cp <= 0.55 ) || ( this.floatState.Cp >= 0.85 ) ) { + var capSum = 0; + loads.fill( 0 ); + for ( var eng = 0; eng < engCapac.length; eng ++ ) { - console.error( "The prismatic coefficient is not being respected. It should be 0.55 < Cp < 0.85, not" + " " + this.floatState.Cp.toFixed( 2 ) + "." ); + capSum += engCapac[ eng ]; + if ( load <= triggerRatio * capSum ) { // if engines can support load - } + for ( i = 0; i <= eng; i ++ ) { // distribute load proportionally to engines' capacities - let Rt = this.coefficients.k * rf + rapp + rw + rb + rtr + ra; + loads[ i ] = load / capSum; + + } + + break; - return Rt; + } - }, "calmResistance" ), - totalResistance: StateModule.prototype.memoized( function () { + } - let Hw = 2 * this.wavCre.waveDef.waveAmplitude; // wave height + } else if ( triggerRatio * partCapac < load && load <= totalCapac ) { // if all engines are loaded above trigger ratio, make them all have same load % - let Rtadd; // N, total resistance including added wave resistance - if ( Hw <= 2 ) { // use Kreitner formula + loads.fill( load / totalCapac ); - let raddw = 0.64 * Math.pow( Hw * this.floatState.BWL, 2 ) * this.floatState.Cb * this.rho * this.g / this.floatState.LWL; - Rtadd = this.calmResistance + raddw; + } else if ( load > totalCapac ) { - } else { // add 20% sea margin + console.error( "Engines are overloaded. Power plant can't provide current required power." ); + loads.fill( 1 ); - Rtadd = 1.2 * this.calmResistance; + } - } + // calculate SFOC value for each activated engine + var SFOC; + for ( i = 0; i < loads.length; i ++ ) { - let Pe = this.coefficients.speedSI * Rtadd; // effective power + if ( loads[ i ] > 0 ) { // if engine is active - return { Rtadd, Pe }; + if ( system.engines[ i ].polOrder === 3 ) { - }, "totalResistance" ), - efficiency: StateModule.prototype.memoized( function () { + SFOC = system.engines[ i ].a * Math.pow( loads[ i ], 3 ) + system.engines[ i ].b * Math.pow( loads[ i ], 2 ) + system.engines[ i ].c * loads[ i ] + system.engines[ i ].d; - let c8; - if ( this.floatState.BWL / this.coefficients.Taft < 5 ) { + } else if ( system.engines[ i ].polOrder === 2 ) { - c8 = this.floatState.BWL * this.coefficients.wa / ( this.floatState.LWL * this.propeller.D * this.coefficients.Taft ); + SFOC = system.engines[ i ].a * Math.pow( loads[ i ], 2 ) + system.engines[ i ].b * loads[ i ] + system.engines[ i ].c; - } else { + } - c8 = this.coefficients.wa * ( 7 * this.floatState.BWL / this.coefficients.Taft - 25 ) / ( this.floatState.LWL * this.propeller.D * ( this.floatState.BWL / this.coefficients.Taft - 3 ) ); + cons += SFOC / ( 1000 ) * loads[ i ] * engCapac[ i ]; // consumption rate in kg/g } - let c9; - if ( c8 < 28 ) { + } - c9 = c8; + return cons; - } else { + } - c9 = 32 - 16 / ( c8 - 24 ); + var consumptionRate; - } + if ( typeof this.powerPlant.auxiliary === "object" ) { // calculate results for vessels which have main and auxiliary power systems - let c11; - if ( this.coefficients.Taft / this.propeller.D < 2 ) { + // change the propeller states for the maneuvering proppeller + consumptionRate = this.powerPlant.main.noSys * shareLoad( powerPlant.main, Pp / ( 1000 * this.powerPlant.main.noSys ) ); + consumptionRate += shareLoad( this.powerPlant.auxiliary, this.auxPowerState.Paux / 1000 ); - c11 = this.coefficients.Taft / this.propeller.D; + } else { // calculate results for vessels which have only one power system - } else { + consumptionRate = shareLoad( this.powerPlant.main, Pp / 1000 ); - c11 = 0.0833333 * Math.pow( this.coefficients.Taft / this.propeller.D, 3 ) + 1.33333; + } - } + return consumptionRate; - let c19; - if ( this.floatState.Cp < 0.7 ) { + } +} ); +Object.defineProperties( Manoeuvring.prototype, { + hydroCoeff: StateModule.prototype.memoized( function () { - c19 = 0.12997 / ( 0.95 - this.floatState.Cb ) - 0.11056 / ( 0.95 - this.floatState.Cp ); + var attributes = this.ship.structure.hull.attributes; + var state = this.states.discrete.FloatingCondition.state; + var calc = this.ship.designState.calculationParameters; - } else { + var L = attributes.LOA; + var D = attributes.Depth; + var B = attributes.BOA; - c19 = 0.18567 / ( 1.3571 - this.floatState.Cm ) - 0.71276 + 0.38648 * this.floatState.Cp; - } + var Cb = calc.Cb_design || state.Cb; + var Vs = state.Vs; + var T = calc.Draft_design; + var rho = this.rho; - let Cp1 = 1.45 * this.floatState.Cp - 0.315 - 0.0225 * this.coefficients.lcb; - let cv = this.coefficients.k * this.coefficients.cf + this.coefficients.ca; - let c20 = 1 + 0.015 * this.cstern; + var Vsdn = Vs / Math.pow( L, 3 ); + var delta_SR = 1 - 0.7 / ( 28.7 * Vsdn + 0.54 ); + var pow = Math.pow; - let w; // wake factor - if ( this.propeller.noProps === 1 ) { + const PI = Math.PI; + const ld = L / D; + const bl = B / L; + const bt = B / T; + const bls = pow( bl, 2 ); + const bts = pow( bt, 2 ); + const tls = pow( T / L, 2 ); - w = c9 * c20 * cv * this.floatState.LWL / this.coefficients.Taft * ( 0.050776 + 0.93405 * c11 * cv / ( 1 - Cp1 ) ) + 0.27915 * c20 * Math.pow( this.floatState.BWL / ( this.floatState.LWL * ( 1 - Cp1 ) ), 0.5 ) + c19 * c20; + // Clarke formulas + var Yvaccdn = - PI * tls * ( 1 + 0.16 * Cb * bt - 5.1 * bls ); + var Yraccdn = - PI * tls * ( 0.67 * bl - 0.0033 * bts ); + var Nvaccdn = - PI * tls * ( 1.1 * bl - 0.041 * bt ); + var Nraccdn = - PI * tls * ( 1 / 12 + 0.017 * Cb * bt - 0.33 * bl ); + var Yvacc = Yvaccdn * 0.5 * rho * pow( L, 3 ); + var Yracc = Yraccdn * 0.5 * rho * pow( L, 4 ); + var Nvacc = Nvaccdn * 0.5 * rho * pow( L, 4 ); + var Nracc = Nraccdn * 0.5 * rho * pow( L, 5 ); - } else if ( this.propeller.noProps === 2 ) { + // Lee formulas + var Yvdn = - ( 0.145 + 2.25 / ld - 0.2 * delta_SR ); + var mdn = this.m / ( 0.5 * rho * pow( L, 2 ) * D ); + var Yrdn = mdn - ( 0.282 + 0.1 * delta_SR ) + ( 0.0086 * delta_SR + 0.004 ) * ld; + var Nvdn = - ( 0.222 + 0.1 * delta_SR ) + 0.00484 * ld; + var Nrdn = - ( 0.0424 - 0.03 * delta_SR ) - ( 0.004 * delta_SR - 0.00027 ) * ld; - w = 0.3095 * this.floatState.Cb + 10 * cv * this.floatState.Cb - 0.23 * this.propeller.D / Math.pow( this.floatState.BWL * this.coefficients.T, 0.5 ); + return { Yvacc, Yracc, Nvacc, Nracc, Yvdn, Yrdn, Nvdn, Nrdn }; - } + }, "hydroCoeff" ), + dn: StateModule.prototype.memoized( function () { - let t; // thrust deduction factor - if ( this.propeller.noProps === 1 ) { + const L = this.ship.structure.hull.attributes.LOA; - t = 0.25014 * Math.pow( this.floatState.BWL / this.floatState.LWL, 0.28956 ) * Math.pow( Math.pow( this.floatState.BWL * this.coefficients.T, 0.5 ) / this.propeller.D, 0.2624 ) / - Math.pow( 1 - this.floatState.Cp + 0.0225 * this.coefficients.lcb, 0.01762 ) + 0.0015 * this.cstern; + var Cl = 0.5 * this.rho * Math.pow( L, 2 ); + var Cll = Cl * L; + var Clll = Cll * L; - } else if ( this.propeller.noProps === 2 ) { + return { Cl, Cll, Clll }; - t = 0.325 * this.floatState.Cb - 0.1885 * this.propeller.D / Math.pow( this.floatState.BWL * this.coefficients.T, 0.5 ); + }, "dn" ) +} ); +function FuelConsumption( ship, states, powerPlant ) { - } + StateModule.call( this, ship, states ); + this.propellerState = this.states.discrete.PropellerInteraction.state; - let etah = ( 1 - t ) / ( 1 - w ); // hull efficiency + this.setAuxPower = function ( Paux ) { - return { w, t, etah }; + if ( typeof Paux === "undefined" ) { - }, "efficiency" ) - } ); + Paux = 0; - } + } -} + if ( typeof this.states.discrete.AuxPower === "undefined" ) { // Ps and Paux in W each -class PropellerInteraction extends StateModule { + this.states.discrete.AuxPower = { + state: {}, + thisStateVer: 0 + }; - constructor( ship, states, propeller, rho = 1025 ) { + } - super( ship, states ); - this.propeller = propeller; + this.states.discrete.AuxPower.state.Paux = Paux; + this.states.discrete.AuxPower.thisStateVer ++; - if ( typeof this.states.discrete.FloatingCondition === "undefined" ) { + }; - this.setDraft(); + if ( typeof this.states.discrete.AuxPower === "undefined" ) { // Ps and Paux in W each - } + this.setAuxPower( 0 ); - if ( typeof this.states.discrete.Speed === "undefined" ) { // if vessel does not have a speed state + } - this.setSpeed(); // use its design speed + this.auxPowerState = this.states.discrete.AuxPower.state; - } + this.powerPlant = powerPlant; + this.output = [ "consumptionRate" ]; - this.speedState = this.states.discrete.Speed.state; - this.floatState = this.states.discrete.FloatingCondition.state; - this.resistanceState = this.states.discrete.HullResistance.state; - this.rho = rho; // kg/m³ - this.output = [ "propulsion" ]; + this.cacheDependence = [ "PropellerInteraction" ]; + this.cache = {}; - this.cacheDependence = [ "FloatingCondition", "Speed" ]; - this.cache = {}; +} - Object.defineProperties( this, { - propulsion: StateModule.prototype.memoized( function () { +FuelConsumption.prototype = Object.create( StateModule.prototype ); - // convert vessel speed from knots to m/s - if ( this.speedSI === 0 ) { +Object.assign( FuelConsumption.prototype, { + constructor: FuelConsumption +} ); - console.error( "Speed equals to zero, try getPropResult() method to get boolard pull or use changeSpeed() method to set a non null value." ); +Object.defineProperties( FuelConsumption.prototype, { + consumptionRate: StateModule.prototype.memoized( function () { // consumption rate in kg/s - } + // share load among engines in a system's array + function shareLoad( system, load ) { + + var triggerRatio = 0.8; // define loading rate at which next engine in the power system will be activated for sharing loads + var cons = 0; + + var engCapac = []; + for ( var i = 0; i < system.engines.length; i ++ ) { + + engCapac[ i ] = system.engines[ i ].MCR; + + } - var speedSI = 0.514444 * this.speedState.speed; - var lcb = 100 * ( this.floatState.LCB - ( this.floatState.minXs + this.floatState.LWL / 2 ) ) / this.floatState.LWL; // % - var Va = speedSI / ( 1 - this.resistanceState.w ); // m/s - var T = this.resistanceState.Rtadd / ( this.propeller.noProps * ( 1 - this.resistanceState.t ) ); // N, thrust + if ( typeof system.etag === "number" ) { // diesel electrical system - var acoeff = T / ( this.rho * Math.pow( this.propeller.D * Va, 2 ) ); - var bcoeff = this.propeller.beta2; - var ccoeff = - this.propeller.beta1; - var J = ( - bcoeff + Math.pow( Math.pow( bcoeff, 2 ) - 4 * acoeff * ccoeff, 0.5 ) ) / ( 2 * acoeff ); + load = load / ( system.etas * system.etag ); - var n = Va / ( J * this.propeller.D ); // rps - // var npm = 60*n; + } else { // diesel mechanical system - var KT = this.propeller.beta1 - this.propeller.beta2 * J; - var KQ = this.propeller.gamma1 - this.propeller.gamma2 * J; - var eta0 = J * KT / ( 2 * Math.PI * KQ ); + load = load / system.etas; // consumption rate in kg/s - var etar; - if ( this.propeller.noProps === 1 ) { + } - etar = 0.9922 - 0.05908 * this.propeller.AeAo + 0.07424 * ( this.floatState.Cp - 0.0225 * lcb ); + // distribute loads among engines + var totalCapac = engCapac.reduce( ( a, b ) => a + b, 0 ); + var partCapac = totalCapac - engCapac[ engCapac.length - 1 ]; + var loads = Array( engCapac.length ); + if ( load <= triggerRatio * partCapac ) { // if not all engines are loaded above trigger ratio, load them according to trigger rate ceil - } else if ( this.propeller.noProps === 2 ) { + var capSum = 0; + loads.fill( 0 ); + for ( var eng = 0; eng < engCapac.length; eng ++ ) { - etar = 0.9737 + 0.111 * ( this.floatState.Cp - 0.0225 * lcb ) - 0.06325 * this.propeller.P / this.propeller.D; + capSum += engCapac[ eng ]; + if ( load <= triggerRatio * capSum ) { // if engines can support load - } + for ( i = 0; i <= eng; i ++ ) { // distribute load proportionally to engines' capacities - var eta = eta0 * this.resistanceState.etah * etar; - var Ps = this.resistanceState.Pe / eta; // W, required brake power + loads[ i ] = load / capSum; - return { eta, Ps, n, Va }; + } - }, "propulsion" ) + break; - } ); + } - } + } -} + } else if ( triggerRatio * partCapac < load && load <= totalCapac ) { // if all engines are loaded above trigger ratio, make them all have same load % -//@EliasHasle + loads.fill( load / totalCapac ); -//Very simple download of the specification of a given ship design. Depends on a working getSpecification method. + } else if ( load > totalCapac ) { -function downloadShip( ship ) { + console.error( "Engines are overloaded. Power plant can't provide current required power." ); + loads.fill( 1 ); - // TODO: Add the possibility to choose the json name and return the link metadata. + } - let specification = ship.getSpecification(); - let output = JSON.stringify( specification ); - let link = document.createElement( "a" ); - link.href = "data:application/json," + encodeURIComponent( output ); - link.download = "shipdesignspecification.json"; - link.target = "_blank"; - link.click(); + // calculate SFOC value for each activated engine + var SFOC; + for ( i = 0; i < loads.length; i ++ ) { -} + if ( loads[ i ] > 0 ) { // if engine is active -class FuelConsumption extends StateModule { + if ( system.engines[ i ].polOrder === 3 ) { - constructor( ship, states, powerPlant ) { + SFOC = system.engines[ i ].a * Math.pow( loads[ i ], 3 ) + system.engines[ i ].b * Math.pow( loads[ i ], 2 ) + system.engines[ i ].c * loads[ i ] + system.engines[ i ].d; - super( ship, states ); + } else if ( system.engines[ i ].polOrder === 2 ) { - this.propellerState = this.states.discrete.PropellerInteraction.state; + SFOC = system.engines[ i ].a * Math.pow( loads[ i ], 2 ) + system.engines[ i ].b * loads[ i ] + system.engines[ i ].c; - this.setAuxPower = function ( Paux ) { + } - if ( typeof Paux === "undefined" ) { + cons += SFOC / ( 1000 * 3600 ) * loads[ i ] * engCapac[ i ]; // consumption rate in kg/s - Paux = 0; + } } - if ( typeof this.states.discrete.AuxPower === "undefined" ) { // Ps and Paux in W each - - this.states.discrete.AuxPower = { - state: {}, - thisStateVer: 0 - }; + return cons; - } + } - this.states.discrete.AuxPower.state.Paux = Paux; - this.states.discrete.AuxPower.thisStateVer ++; + var consumptionRate; + if ( typeof this.powerPlant.auxiliary === "object" ) { // calculate results for vessels which have main and auxiliary power systems - }; + consumptionRate = this.powerPlant.main.noSys * shareLoad( this.powerPlant.main, this.propellerState.Ps / ( 1000 * this.powerPlant.main.noSys ) ); + consumptionRate += shareLoad( this.powerPlant.auxiliary, this.auxPowerState.Paux / 1000 ); - if ( typeof this.states.discrete.AuxPower === "undefined" ) { // Ps and Paux in W each + } else { // calculate results for vessels which have only one power system - this.setAuxPower( 0 ); + consumptionRate = shareLoad( this.powerPlant.main, ( this.propellerState.Ps + this.auxPowerState.Paux ) / 1000 ); } - this.auxPowerState = this.states.discrete.AuxPower.state; + return consumptionRate; - this.powerPlant = powerPlant; - this.output = [ "consumptionRate" ]; + } ) +}, "consumptionRate" ); +// partially adapted from http://www.shiplab.ntnu.co/app/holtrop/ - this.cacheDependence = [ "PropellerInteraction" ]; - this.cache = {}; +function HullResistance( ship, states, propeller, wavCre, g = 9.81, rho = 1025, mi = 0.00122 ) { - Object.defineProperties( this, { + StateModule.call( this, ship, states ); + this.propeller = propeller; + if ( typeof this.states.discrete.FloatingCondition === "undefined" ) { - consumptionRate: StateModule.prototype.memoized( function () { // consumption rate in kg/s + this.setDraft(); - // share load among engines in a system's array - function shareLoad( system, load ) { + } - var triggerRatio = 0.8; // define loading rate at which next engine in the power system will be activated for sharing loads - var cons = 0; + if ( typeof this.states.discrete.Speed === "undefined" ) { // if vessel does not have a speed state - var engCapac = []; - for ( var i = 0; i < system.engines.length; i ++ ) { + this.setSpeed(); // use its design speed - engCapac[ i ] = system.engines[ i ].MCR; + } - } + this.speedState = this.states.discrete.Speed.state; + this.floatState = this.states.discrete.FloatingCondition.state; + this.wavCre = wavCre; + this.g = g; + this.rho = rho; + this.mi = mi; // dynamic viscosity + this.b = this.ship.structure.hull.attributes.bulb; // has a bulb? Boolean. + this.tr = this.ship.structure.hull.attributes.transom; // transom. Boolean. + this.cstern = this.ship.structure.hull.attributes.cstern; // afterbody form. + // Pram with Gondola = -25, V-Shaped Sections = -10, Normal Section Shape = 0, U-Shaped Sections with Hognes Stern = 10 + this.appendices = this.ship.structure.hull.attributes.appendices; // appendices information + // skegRudder has coeff from 1.5 to 2.0 + // sternRudder has coeff from 1.3 to 1.5 + // twinScrewBalanceRudder has coeff 2.8 + // shaftBrackets has coeff 3.0 + // skeg has coeff from 1.5 to 2.0 + // strutBossings has coeff 3.0 + // hullBossings has coeff 2.0 + // shafts have coeff from 2 to 4 + // stabilizerFins has coeff 2.8 + // dome has coeff 2.7 + // bilgeKeel has coeff 1.4 + this.output = [ "totalResistance", "efficiency" ]; - if ( typeof system.etag === "number" ) { // diesel electrical system + this.cacheDependence = [ "FloatingCondition", "Speed" ]; + this.cache = {}; - load = load / ( system.etas * system.etag ); +} - } else { // diesel mechanical system +HullResistance.prototype = Object.create( StateModule.prototype ); - load = load / system.etas; // consumption rate in kg/s +Object.assign( HullResistance.prototype, { + constructor: HullResistance +} ); - } +Object.defineProperties( HullResistance.prototype, { + coefficients: StateModule.prototype.memoized( function () { - // distribute loads among engines - var totalCapac = engCapac.reduce( ( a, b ) => a + b, 0 ); - var partCapac = totalCapac - engCapac[ engCapac.length - 1 ]; - var loads = Array( engCapac.length ); - if ( load <= triggerRatio * partCapac ) { // if not all engines are loaded above trigger ratio, load them according to trigger rate ceil + var lcb = 100 * ( this.floatState.LCB - ( this.floatState.minXs + this.floatState.LWL / 2 ) ) / this.floatState.LWL; // % - var capSum = 0; - loads.fill( 0 ); - for ( var eng = 0; eng < engCapac.length; eng ++ ) { + var Tfore; + if ( this.floatState.draftfp === null ) { - capSum += engCapac[ eng ]; - if ( load <= triggerRatio * capSum ) { // if engines can support load + Tfore = this.floatState.T; - for ( i = 0; i <= eng; i ++ ) { // distribute load proportionally to engines' capacities + } else { - loads[ i ] = load / capSum; + Tfore = this.floatState.draftfp; - } + } - break; + var Taft; + if ( this.floatState.draftap === null ) { - } + Taft = this.floatState.T; - } + } else { + + Taft = this.floatState.draftap; - } else if ( triggerRatio * partCapac < load && load <= totalCapac ) { // if all engines are loaded above trigger ratio, make them all have same load % + } - loads.fill( load / totalCapac ); + var T = ( Tfore + Taft ) / 2; // m, average draft + var hb = Tfore / 2; + var abt = Math.PI * Math.pow( Tfore / 2, 2 ) * this.b / 7.7; // transverse sectional bulb area + var c3 = 0.56 * ( Math.pow( abt, 1.5 ) ) / ( this.floatState.BWL * T * ( 0.31 * Math.pow( abt, 0.5 ) + Tfore - hb ) ); + var c2 = Math.exp( - 1.89 * Math.pow( c3, 0.5 ) ); - } else if ( load > totalCapac ) { + var c4; + if ( Tfore / this.floatState.LWL > 0.04 ) { - console.error( "Engines are overloaded. Power plant can't provide current required power." ); - loads.fill( 1 ); + c4 = 0.04; - } + } else { - // calculate SFOC value for each activated engine - var SFOC; - for ( i = 0; i < loads.length; i ++ ) { + c4 = Tfore / this.floatState.LWL; - if ( loads[ i ] > 0 ) { // if engine is active + } - if ( system.engines[ i ].polOrder === 3 ) { + // correlation allowance coefficient + var ca = 0.006 * Math.pow( this.floatState.LWL + 100, - 0.16 ) - 0.00205 + 0.003 * Math.pow( this.floatState.LWL / 7.5, 0.5 ) * Math.pow( this.floatState.Cb, 4 ) * c2 * ( 0.04 - c4 ); + var wa = this.floatState.LWL * ( 2 * T + this.floatState.BWL ) * Math.pow( this.floatState.Cm, 0.5 ) * ( 0.453 + 0.4425 * this.floatState.Cb - 0.2862 * this.floatState.Cm - 0.003467 * + this.floatState.BWL / T + 0.3696 * this.floatState.Cwp ) + 2.38 * abt / this.floatState.Cb; // wetted area - SFOC = system.engines[ i ].a * Math.pow( loads[ i ], 3 ) + system.engines[ i ].b * Math.pow( loads[ i ], 2 ) + system.engines[ i ].c * loads[ i ] + system.engines[ i ].d; + var lr = this.floatState.LWL * ( 1 - this.floatState.Cp + ( 0.06 * this.floatState.Cp * ( lcb / 100 ) / ( 4 * this.floatState.Cp - 1 ) ) ); + var c14 = 1 + 0.011 * this.cstern; + var k = 0.93 + ( 0.487118 * c14 * Math.pow( this.floatState.BWL / this.floatState.LWL, 1.06806 ) * Math.pow( T / this.floatState.LWL, 0.46106 ) * Math.pow( this.floatState.LWL / + lr, 0.121563 ) * Math.pow( Math.pow( this.floatState.LWL, 3 ) / this.floatState.Vs, 0.36486 ) * Math.pow( 1 - this.floatState.Cp, - 0.604247 ) ); // form factor - } else if ( system.engines[ i ].polOrder === 2 ) { + var speedSI = 0.514444 * this.speedState.speed; // convert the speed from knots to m/s + var re = this.rho * this.floatState.LWL * speedSI / this.mi; // Reynolds number + var cf = 0.075 / Math.pow( ( Math.log( re ) / Math.log( 10 ) ) - 2, 2 ); // frictional coefficient - SFOC = system.engines[ i ].a * Math.pow( loads[ i ], 2 ) + system.engines[ i ].b * loads[ i ] + system.engines[ i ].c; + return { lcb, Tfore, Taft, T, hb, c2, ca, abt, wa, lr, k, speedSI, cf }; - } + }, "coefficients" ), + calmResistance: StateModule.prototype.memoized( function () { // N, total hull resistance in calm waters - cons += SFOC / ( 1000 * 3600 ) * loads[ i ] * engCapac[ i ]; // consumption rate in kg/s + var at = 0.95 * ( this.coefficients.Taft - this.coefficients.Taft * 0.9225 ) * this.floatState.BWL * 0.89 * this.tr; // transom stern area + var rf = 0.5 * this.rho * Math.pow( this.coefficients.speedSI, 2 ) * this.coefficients.wa * this.coefficients.cf; // frictional resistance - } + var fnt; + if ( at === 0 ) { - } + fnt = 0; - return cons; + } else { - } + fnt = this.coefficients.speedSI / ( Math.pow( 2 * this.g * at / ( this.floatState.BWL + this.floatState.BWL * this.floatState.Cwp ), 0.5 ) ); - var consumptionRate; - if ( typeof this.powerPlant.auxiliary === "object" ) { // calculate results for vessels which have main and auxiliary power systems + } - consumptionRate = this.powerPlant.main.noSys * shareLoad( this.powerPlant.main, this.propellerState.Ps / ( 1000 * this.powerPlant.main.noSys ) ); - consumptionRate += shareLoad( this.powerPlant.auxiliary, this.auxPowerState.Paux / 1000 ); + var c6; + if ( fnt < 5 ) { - } else { // calculate results for vessels which have only one power system + c6 = 0.2 * ( 1 - 0.2 * fnt ); - consumptionRate = shareLoad( this.powerPlant.main, ( this.propellerState.Ps + this.auxPowerState.Paux ) / 1000 ); + } else { - } + c6 = 0; - return consumptionRate; + } - }, "consumptionRate" ) + var rtr = 0.5 * this.rho * Math.pow( this.coefficients.speedSI, 2 ) * at * c6; // stern resistance + var sapp = 0; + var mult = 0; + for ( var prop in this.appendices ) { - } ); + sapp += this.appendices[ prop ].area; + mult += this.appendices[ prop ].coeff * this.appendices[ prop ].area; - } + } -} + var k2; + if ( sapp !== 0 ) { -class Positioning { + k2 = mult / sapp; - constructor( ship, states, path ) { + } else { - this.ship = ship; - this.states = states; + k2 = 0; - this.routeData = {}; - this.routeData.pathVec = []; - this.routeData.unitPath = []; - this.routeData.heading = []; - this.routeData.totalDist = 0; - this.routeData.legDistance = []; - for ( var leg = 0; leg < path.length - 1; leg ++ ) { + } - this.routeData.pathVec[ leg ] = path[ leg + 1 ].map( ( num, idx ) => num - path[ leg ][ idx ] ); - this.routeData.legDistance[ leg ] = Math.sqrt( this.routeData.pathVec[ leg ].map( ( num ) => Math.pow( num, 2 ) ).reduce( ( num1, num2 ) => num1 + num2 ) ); - this.routeData.unitPath[ leg ] = this.routeData.pathVec[ leg ].map( ( num ) => num / this.routeData.legDistance[ leg ] ); + var rapp = 0.5 * this.rho * Math.pow( this.coefficients.speedSI, 2 ) * sapp * k2 * this.coefficients.cf; // appendage resistance + var fn = this.coefficients.speedSI / Math.pow( this.g * this.floatState.LWL, 0.5 ); //Froude number - // get heading in relation to North/y axis - var heading = Math.acos( this.routeData.pathVec[ leg ][ 1 ] / this.routeData.legDistance[ leg ] ); - heading *= 180 / Math.PI; // convert to degrees + var c7; + if ( this.floatState.BWL / this.floatState.LWL < 0.11 ) { - if ( this.routeData.pathVec[ leg ][ 0 ] < 0 ) { + c7 = 0.229577 * Math.pow( this.floatState.BWL / this.floatState.LWL, 0.33333 ); - heading = 360 - heading; + } else if ( this.floatState.BWL / this.floatState.LWL < 0.25 ) { - } + c7 = this.floatState.BWL / this.floatState.LWL; - this.routeData.heading[ leg ] = heading; + } else { - this.routeData.totalDist += this.routeData.legDistance[ leg ]; + c7 = 0.5 - 0.0625 * this.floatState.LWL / this.floatState.BWL; } - // initialize vessel on path - this.states.continuous.Positioning = {}; - this.states.continuous.Positioning.position = path[ 0 ]; - this.states.continuous.Positioning.travelLegDist = 0; - this.states.continuous.Positioning.travelDist = 0; - this.positionState = this.states.continuous.Positioning; + // calculate the half angle of entrance + var ie = 1 + 89 * Math.exp( - Math.pow( this.floatState.LWL / this.floatState.BWL, 0.80856 ) * Math.pow( 1 - this.floatState.Cwp, 0.30484 ) * Math.pow( 1 - this.floatState.Cp - 0.0225 * + ( this.coefficients.lcb / 100 ), 0.6367 ) * Math.pow( this.coefficients.lr / this.floatState.BWL, 0.34574 ) * Math.pow( 100 * ( this.floatState.Vs / Math.pow( this.floatState.LWL, 3 ) ), 0.16302 ) ); + var c1 = 2223105 * Math.pow( c7, 3.78613 ) * Math.pow( this.coefficients.T / this.floatState.BWL, 1.07961 ) * Math.pow( 90 - ie, - 1.37565 ); + var c5 = 1 - ( 0.8 * at ) / ( this.floatState.BWL * this.coefficients.T * this.floatState.Cm ); - this.states.discrete.Leg = { - state: { - leg: 0 - }, - thisStateVer: 1 - }; - this.legState = this.states.discrete.Leg.state; + var c15; + if ( Math.pow( this.floatState.LWL, 3 ) / this.floatState.Vs < 512 ) { - if ( typeof this.states.discrete.Speed === "undefined" ) { // if vessel does not have a speed state + c15 = - 1.69385; - StateModule.prototype.setSpeed.call( this ); // use its design speed + } else if ( Math.pow( this.floatState.LWL, 3 ) / this.floatState.Vs < 1726.91 ) { - } + c15 = - 1.69385 + ( this.floatState.LWL / Math.pow( this.floatState.Vs, 1 / 3 ) - 8 ) / 2.36; - if ( typeof this.states.discrete.Heading === "undefined" ) { + } else { - StateModule.prototype.setHeading.call( this, this.routeData.heading[ 0 ] ); + c15 = 0; } - this.advanceShip = function ( timeStep ) { // calculate advanced distance during one time step + var c16; + if ( this.floatState.Cp < 0.8 ) { + + c16 = 8.07981 * this.floatState.Cp - 13.8673 * Math.pow( this.floatState.Cp, 2 ) + 6.984388 * Math.pow( this.floatState.Cp, 3 ); + + } else { - var remVec, remDist; - var advDist = timeStep * 1 / 3600 * this.states.discrete.Speed.state.speed; + c16 = 1.73014 - 0.7067 * this.floatState.Cp; - while ( 0 < advDist ) { + } - remVec = path[ this.legState.leg + 1 ].map( ( num, idx ) => num - this.positionState.position[ idx ] ); - remDist = Math.sqrt( remVec.map( ( num ) => Math.pow( num, 2 ) ).reduce( ( num1, num2 ) => num1 + num2 ) ); - if ( advDist <= remDist ) { + var m1 = 0.0140407 * ( this.floatState.LWL / this.coefficients.T ) - 1.75254 * ( ( Math.pow( this.floatState.Vs, 1 / 3 ) ) / this.floatState.LWL ) - 4.79323 * ( this.floatState.BWL / this.floatState.LWL ) - c16; - this.positionState.position = this.positionState.position.map( ( num, idx ) => num + advDist * this.routeData.unitPath[ this.legState.leg ][ idx ] ); + var lambda; + if ( this.floatState.LWL / this.floatState.BWL > 12 ) { - // add to traveled distance - this.positionState.travelLegDist = this.positionState.travelLegDist + advDist; - this.positionState.travelDist = this.positionState.travelDist + advDist; + lambda = 1.446 * this.floatState.Cp - 0.36; - advDist = 0; + } else { - } else if ( path[ this.legState.leg + 2 ] !== undefined ) { // trip has another leg + lambda = 1.446 * this.floatState.Cp - 0.03 * ( this.floatState.LWL / this.floatState.BWL ); - // change direction and continue sailing - advDist -= remDist; - this.positionState.position = path[ this.legState.leg + 1 ]; + } - // add to traveled distance - this.positionState.travelLegDist = 0; - this.positionState.travelDist = this.positionState.travelDist + remDist; + var c17 = 6919.3 * Math.pow( this.floatState.Cm, - 1.3346 ) * Math.pow( this.floatState.Vs / Math.pow( this.floatState.LWL, 3 ), 2.00977 ) * Math.pow( this.floatState.LWL / this.floatState.BWL - 2, 1.40692 ); + var m3 = - 7.2035 * Math.pow( this.floatState.BWL / this.floatState.LWL, 0.326869 ) * Math.pow( this.coefficients.T / this.floatState.BWL, 0.605375 ); + var m4_0_4 = c15 * 0.4 * Math.exp( - 0.034 * Math.pow( 0.4, - 3.29 ) ); + var rwa_0_4 = c1 * this.coefficients.c2 * c5 * this.floatState.Vs * this.rho * this.g * Math.exp( m1 * Math.pow( 0.4, - 0.9 ) + m4_0_4 * Math.cos( lambda * Math.pow( 0.4, - 2 ) ) ); + var m4_0_55 = c15 * 0.4 * Math.exp( - 0.034 * Math.pow( 0.55, - 3.29 ) ); + var rwa_0_55 = c17 * this.coefficients.c2 * c5 * this.floatState.Vs * this.rho * this.g * Math.exp( m3 * Math.pow( 0.55, - 0.9 ) + m4_0_55 * Math.cos( lambda * + Math.pow( 0.55, - 2 ) ) ); - this.legState.leg ++; - this.states.discrete.Leg.thisStateVer ++; + var m4, rwa, rwb, rwab; + if ( fn === 0 ) { - StateModule.prototype.setHeading.call( this, this.routeData.heading[ this.legState.leg ] ); - console.log( "Vessel reached pivot point in navigation and entered leg " + this.legState.leg + "." ); + m4 = 0; + rwa = 0; // wave resistance for Froude < 0.4 + rwb = 0; // wave resistance for Froude > 0.55 + rwab = 0; - } else { + } else { - this.positionState.position = this.positionState.position.map( ( num, idx ) => num + advDist * this.routeData.unitPath[ this.legState.leg ][ idx ] ); + m4 = c15 * 0.4 * Math.exp( - 0.034 * Math.pow( fn, - 3.29 ) ); + rwa = c1 * this.coefficients.c2 * c5 * this.floatState.Vs * this.rho * this.g * Math.exp( m1 * Math.pow( fn, - 0.9 ) + m4 * Math.cos( lambda * Math.pow( fn, - 2 ) ) ); + rwb = c17 * this.coefficients.c2 * c5 * this.floatState.Vs * this.rho * this.g * Math.exp( m3 * Math.pow( fn, - 0.9 ) + m4 * Math.cos( lambda * Math.pow( fn, - 2 ) ) ); + rwab = rwa_0_4 + ( 10 * fn - 4 ) * ( rwa_0_55 - rwa_0_4 ) / 1.5; - // add to traveled distance - this.positionState.travelLegDist = this.positionState.travelLegDist + advDist; - this.positionState.travelDist = this.positionState.travelDist + advDist; + } - console.log( "Vessel reached final destination." ); - break; + var rw; + if ( fn < 0.4 ) { - } + rw = rwa; - } + } else if ( fn <= 0.55 ) { - }; + rw = rwab; - } + } else { -} + rw = rwb; -// @ferrari212 + } -class Manoeuver extends StateModule { + var fni = this.coefficients.speedSI / Math.sqrt( this.g * ( this.coefficients.Tfore - this.coefficients.hb - 0.25 * Math.pow( this.coefficients.abt, 0.5 ) ) + ( 0.15 * Math.pow( this.coefficients.speedSI, 2 ) ) ); + var pb = ( 0.56 * Math.pow( this.coefficients.abt, 0.5 ) ) / ( this.coefficients.Tfore - 1.5 * this.coefficients.hb ); - constructor( ship, states, hullResistance, propellerInteraction, fuelConsumption, manoeuvring, rho = 1025 ) { + var rb; + if ( this.coefficients.abt === 0 ) { - super( ship, states ); + rb = 0; - if ( typeof this.states.discrete.FloatingCondition === "undefined" ) { + } else { - this.setDraft(); + rb = ( 0.11 * Math.exp( - 3 * Math.pow( pb, - 2 ) ) * Math.pow( fni, 3 ) * Math.pow( this.coefficients.abt, 1.5 ) * this.rho * this.g ) / ( 1 + Math.pow( fni, 2 ) ); } - if ( typeof this.states.discrete.Speed === "undefined" ) { // if vessel does not have a speed state + var ra = 0.91 * 0.5 * this.rho * Math.pow( this.coefficients.speedSI, 2 ) * this.coefficients.wa * this.coefficients.ca; + if ( ( this.floatState.LWL / this.floatState.BWL <= 3.9 ) || ( this.floatState.LWL / this.floatState.BWL >= 15 ) ) { - this.setSpeed(); // use its design speed + console.error( "The L/B relation is not being respected. It should be 3.9 < L/B < 15, not" + " " + ( this.floatState.LWL / this.floatState.BWL ).toFixed( 2 ) + "." ); } - this.hullRes = hullResistance; - this.propellerInteraction = propellerInteraction; - this.fuelConsumption = fuelConsumption; - this.powerPlant = fuelConsumption.powerPlant; - this.manoeuvring = manoeuvring; - this.state = {}; - this.rho = propellerInteraction.rho; - this.propeller = this.propellerInteraction.propeller; - this.speedState = this.states.discrete.Speed.state; - this.floatState = this.states.discrete.FloatingCondition.state; - this.resistanceState = this.states.discrete.HullResistance.state; + if ( ( this.floatState.BWL / this.coefficients.T <= 2.1 ) || ( this.floatState.BWL / this.coefficients.T >= 4 ) ) { - const YAW = manoeuvring.initial_yaw || 0; - const AN = manoeuvring.initial_angle || 0; - // The modules bellow use the value in knots for the ship speed, - // for the maneuvering model it is going to be used the values in SI (m/s). @ferrari212 - Object.assign( this.states, { - DX: { x: 0, y: 0, yaw: 0 }, - V: { u: 0, v: 0, yaw_dot: 0 }, - n: 0, - yaw: YAW, - rudderAngle: AN, - load: 0 - } ); + console.error( "The B/T relation is not being respected. It should be 2.1 < B/T < 4, not" + " " + ( this.floatState.BWL / this.coefficients.T ).toFixed( 2 ) + "." ); - var engines = this.powerPlant.main.engines; - this.powerPlant.engCapac = []; - var engCapac = this.powerPlant.engCapac; + } - for ( var i = 0; i < engines.length; i ++ ) { + if ( ( this.floatState.Cp <= 0.55 ) || ( this.floatState.Cp >= 0.85 ) ) { - engCapac[ i ] = engines[ i ].MCR; + console.error( "The prismatic coefficient is not being respected. It should be 0.55 < Cp < 0.85, not" + " " + this.floatState.Cp.toFixed( 2 ) + "." ); } - this.totalCapac = engCapac.reduce( ( a, b ) => a + b, 0 ); - - const W = ship.getWeight(); - this.m = manoeuvring.m || W.mass; + var Rt = this.coefficients.k * rf + rapp + rw + rb + rtr + ra; - // The approximation is given by the inertia of an Ellipsoid in water - var attributes = this.ship.structure.hull.attributes; - var T = this.ship.designState.calculationParameters.Draft_design; - var approxI = manoeuvring.I || Math.PI * rho * attributes.LOA * attributes.BOA * T * ( 4 * Math.pow( T, 2 ) + Math.pow( attributes.BOA, 2 ) ) / 120; + return Rt; - this.M_RB = manoeuvring.M || [ - [ this.m, 0, 0 ], - [ 0, this.m, 0 ], - [ 0, 0, approxI ] - ]; + }, "calmResistance" ), + totalResistance: StateModule.prototype.memoized( function () { - this.output = [ "hydroCoeff", "dn" ]; + var Hw = 2 * this.wavCre.waveDef.waveAmplitude; // wave height - this.cacheDependence = [ "PropellerInteraction", "FloatingCondition" ]; - this.cache = {}; + var Rtadd; // N, total resistance including added wave resistance + if ( Hw <= 2 ) { // use Kreitner formula - Object.defineProperties( this, { - hydroCoeff: StateModule.prototype.memoized( function () { + var raddw = 0.64 * Math.pow( Hw * this.floatState.BWL, 2 ) * this.floatState.Cb * this.rho * this.g / this.floatState.LWL; + Rtadd = this.calmResistance + raddw; - var attributes = this.ship.structure.hull.attributes; - var state = this.states.discrete.FloatingCondition.state; - var calc = this.ship.designState.calculationParameters; + } else { // add 20% sea margin - var L = attributes.LOA; - var D = attributes.Depth; - var B = attributes.BOA; + Rtadd = 1.2 * this.calmResistance; + } - var Cb = calc.Cb_design || state.Cb; - var Vs = state.Vs; - var T = calc.Draft_design; - var rho = this.rho; + var Pe = this.coefficients.speedSI * Rtadd; // effective power - var Vsdn = Vs / Math.pow( L, 3 ); - var delta_SR = 1 - 0.7 / ( 28.7 * Vsdn + 0.54 ); - var pow = Math.pow; + return { Rtadd, Pe }; - const PI = Math.PI; - const ld = L / D; - const bl = B / L; - const bt = B / T; - const bls = pow( bl, 2 ); - const bts = pow( bt, 2 ); - const tls = pow( T / L, 2 ); + }, "totalResistance" ), + efficiency: StateModule.prototype.memoized( function () { - // Clarke formulas - var Yvaccdn = - PI * tls * ( 1 + 0.16 * Cb * bt - 5.1 * bls ); - var Yraccdn = - PI * tls * ( 0.67 * bl - 0.0033 * bts ); - var Nvaccdn = - PI * tls * ( 1.1 * bl - 0.041 * bt ); - var Nraccdn = - PI * tls * ( 1 / 12 + 0.017 * Cb * bt - 0.33 * bl ); - var Yvacc = Yvaccdn * 0.5 * rho * pow( L, 3 ); - var Yracc = Yraccdn * 0.5 * rho * pow( L, 4 ); - var Nvacc = Nvaccdn * 0.5 * rho * pow( L, 4 ); - var Nracc = Nraccdn * 0.5 * rho * pow( L, 5 ); + var c8; + if ( this.floatState.BWL / this.coefficients.Taft < 5 ) { - // Lee formulas - var Yvdn = - ( 0.145 + 2.25 / ld - 0.2 * delta_SR ); - var mdn = this.m / ( 0.5 * rho * pow( L, 2 ) * D ); - var Yrdn = mdn - ( 0.282 + 0.1 * delta_SR ) + ( 0.0086 * delta_SR + 0.004 ) * ld; - var Nvdn = - ( 0.222 + 0.1 * delta_SR ) + 0.00484 * ld; - var Nrdn = - ( 0.0424 - 0.03 * delta_SR ) - ( 0.004 * delta_SR - 0.00027 ) * ld; + c8 = this.floatState.BWL * this.coefficients.wa / ( this.floatState.LWL * this.propeller.D * this.coefficients.Taft ); - return { Yvacc, Yracc, Nvacc, Nracc, Yvdn, Yrdn, Nvdn, Nrdn }; + } else { - }, "hydroCoeff" ), - dn: StateModule.prototype.memoized( function () { + c8 = this.coefficients.wa * ( 7 * this.floatState.BWL / this.coefficients.Taft - 25 ) / ( this.floatState.LWL * this.propeller.D * ( this.floatState.BWL / this.coefficients.Taft - 3 ) ); - const L = this.ship.structure.hull.attributes.LOA; + } - var Cl = 0.5 * this.rho * Math.pow( L, 2 ); - var Cll = Cl * L; - var Clll = Cll * L; + var c9; + if ( c8 < 28 ) { - return { Cl, Cll, Clll }; + c9 = c8; - }, "dn" ) - } ); + } else { - } + c9 = 32 - 16 / ( c8 - 24 ); + } - // The function simplify the resistance curve by Rt = k*u^2 - // the interpolation could be improved by other types of functions interpolation @ferrari212 - getRes( man ) { + var c11; + if ( this.coefficients.Taft / this.propeller.D < 2 ) { - let pow = Math.pow; + c11 = this.coefficients.Taft / this.propeller.D; - const U = ( Boolean( man.states.calculationParameters.speed ) ) ? man.states.calculationParameters.speed : 10; + } else { - man.hullRes.setSpeed( U ); - const CONV = 0.5144447; - const R = man.hullRes.totalResistance.Rtadd; + c11 = 0.0833333 * Math.pow( this.coefficients.Taft / this.propeller.D, 3 ) + 1.33333; - let k = R / ( Math.pow( U * CONV, 2 ) ); + } + var c19; + if ( this.floatState.Cp < 0.7 ) { - let getRes = function ( u ) { + c19 = 0.12997 / ( 0.95 - this.floatState.Cb ) - 0.11056 / ( 0.95 - this.floatState.Cp ); - return k * ( pow( u, 2 ) ) * Math.sign( u ); + } else { - }; + c19 = 0.18567 / ( 1.3571 - this.floatState.Cm ) - 0.71276 + 0.38648 * this.floatState.Cp; - return getRes; + } - } + var Cp1 = 1.45 * this.floatState.Cp - 0.315 - 0.0225 * this.coefficients.lcb; + var cv = this.coefficients.k * this.coefficients.cf + this.coefficients.ca; + var c20 = 1 + 0.015 * this.cstern; - getPropResult( n ) { + var w; // wake factor + if ( this.propeller.noProps === 1 ) { - if ( n === 0 ) return { Fp: 0, Pp: 0, cons: 0 }; + w = c9 * c20 * cv * this.floatState.LWL / this.coefficients.Taft * ( 0.050776 + 0.93405 * c11 * cv / ( 1 - Cp1 ) ) + 0.27915 * c20 * Math.pow( this.floatState.BWL / ( this.floatState.LWL * ( 1 - Cp1 ) ), 0.5 ) + c19 * c20; - var Va = this.propellerInteraction.propulsion.Va; + } else if ( this.propeller.noProps === 2 ) { - var lcb = 100 * ( this.floatState.LCB - ( this.floatState.minXs + this.floatState.LWL / 2 ) ) / this.floatState.LWL; // % - var J = Math.abs( Va / ( n * this.propeller.D ) ); + w = 0.3095 * this.floatState.Cb + 10 * cv * this.floatState.Cb - 0.23 * this.propeller.D / Math.pow( this.floatState.BWL * this.coefficients.T, 0.5 ); - var KT = this.propeller.beta1 - this.propeller.beta2 * J; - var KQ = this.propeller.gamma1 - this.propeller.gamma2 * J; + } - var etar; + var t; // thrust deduction factor if ( this.propeller.noProps === 1 ) { - etar = 0.9922 - 0.05908 * this.propeller.AeAo + 0.07424 * ( this.floatState.Cp - 0.0225 * lcb ); + t = 0.25014 * Math.pow( this.floatState.BWL / this.floatState.LWL, 0.28956 ) * Math.pow( Math.pow( this.floatState.BWL * this.coefficients.T, 0.5 ) / this.propeller.D, 0.2624 ) / + Math.pow( 1 - this.floatState.Cp + 0.0225 * this.coefficients.lcb, 0.01762 ) + 0.0015 * this.cstern; } else if ( this.propeller.noProps === 2 ) { - etar = 0.9737 + 0.111 * ( this.floatState.Cp - 0.0225 * lcb ) - 0.06325 * this.propeller.P / this.propeller.D; + t = 0.325 * this.floatState.Cb - 0.1885 * this.propeller.D / Math.pow( this.floatState.BWL * this.coefficients.T, 0.5 ); } - var T = KT * this.rho * Math.pow( n, 2 ) * Math.pow( this.propeller.D, 4 ); - var Q = KQ * this.rho * Math.pow( n, 2 ) * Math.pow( this.propeller.D, 5 ); - // console.log( `T: ${T}; Q: ${Q}`); - - var Fp = Math.sign( n ) * T * this.propeller.noProps * ( 1 - this.resistanceState.t ); - var Po = 2 * Math.PI * Math.abs( Q * n ) * this.propeller.noProps; - var Pp = Po * etar; - - var cons = this.getFuelCons( Pp ); - - return { Fp, Pp, cons }; + var etah = ( 1 - t ) / ( 1 - w ); // hull efficiency - } + return { w, t, etah }; - getFuelCons( Pp ) { + }, "efficiency" ) +} ); +// This module simulates the propeller and its interaction with hull and engine. - // share load among engines in a system's array - function shareLoad( system, load ) { +function PropellerInteraction( ship, states, propeller, rho = 1025 ) { - var triggerRatio = 0.8; // define loading rate at which next engine in the power system will be activated for sharing loads - var cons = 0; + StateModule.call( this, ship, states ); // get resistance results in N, W from vessel state + this.propeller = propeller; + if ( typeof this.states.discrete.FloatingCondition === "undefined" ) { - var engCapac = []; - for ( var i = 0; i < system.engines.length; i ++ ) { + this.setDraft(); - engCapac[ i ] = system.engines[ i ].MCR; + } - } + if ( typeof this.states.discrete.Speed === "undefined" ) { // if vessel does not have a speed state - if ( typeof system.etag === "number" ) { // diesel electrical system + this.setSpeed(); // use its design speed - load = load / ( system.etas * system.etag ); + } - } else { // diesel mechanical system + this.speedState = this.states.discrete.Speed.state; + this.floatState = this.states.discrete.FloatingCondition.state; + this.resistanceState = this.states.discrete.HullResistance.state; + this.rho = rho; // kg/m³ + this.output = [ "propulsion" ]; - load = load / system.etas; // consumption rate in kg/s + this.cacheDependence = [ "FloatingCondition", "Speed" ]; + this.cache = {}; - } +} - // distribute loads among engines - var totalCapac = engCapac.reduce( ( a, b ) => a + b, 0 ); - var partCapac = totalCapac - engCapac[ engCapac.length - 1 ]; - var loads = Array( engCapac.length ); - if ( load <= triggerRatio * partCapac ) { // if not all engines are loaded above trigger ratio, load them according to trigger rate ceil +PropellerInteraction.prototype = Object.create( StateModule.prototype ); - var capSum = 0; - loads.fill( 0 ); - for ( var eng = 0; eng < engCapac.length; eng ++ ) { +Object.assign( PropellerInteraction.prototype, { + constructor: PropellerInteraction, +} ); - capSum += engCapac[ eng ]; - if ( load <= triggerRatio * capSum ) { // if engines can support load +Object.defineProperties( PropellerInteraction.prototype, { + propulsion: StateModule.prototype.memoized( function () { - for ( i = 0; i <= eng; i ++ ) { // distribute load proportionally to engines' capacities + // convert vessel speed from knots to m/s + if ( this.speedSI === 0 ) { - loads[ i ] = load / capSum; + console.error( "Speed equals to zero, try getPropResult() method to get boolard pull or use changeSpeed() method to set a non null value." ); - } + } - break; + var speedSI = 0.514444 * this.speedState.speed; + var lcb = 100 * ( this.floatState.LCB - ( this.floatState.minXs + this.floatState.LWL / 2 ) ) / this.floatState.LWL; // % + var Va = speedSI / ( 1 - this.resistanceState.w ); // m/s + var T = this.resistanceState.Rtadd / ( this.propeller.noProps * ( 1 - this.resistanceState.t ) ); // N, thrust - } + var acoeff = T / ( this.rho * Math.pow( this.propeller.D * Va, 2 ) ); + var bcoeff = this.propeller.beta2; + var ccoeff = - this.propeller.beta1; + var J = ( - bcoeff + Math.pow( Math.pow( bcoeff, 2 ) - 4 * acoeff * ccoeff, 0.5 ) ) / ( 2 * acoeff ); - } + var n = Va / ( J * this.propeller.D ); // rps + // var npm = 60*n; - } else if ( triggerRatio * partCapac < load && load <= totalCapac ) { // if all engines are loaded above trigger ratio, make them all have same load % + var KT = this.propeller.beta1 - this.propeller.beta2 * J; + var KQ = this.propeller.gamma1 - this.propeller.gamma2 * J; + var eta0 = J * KT / ( 2 * Math.PI * KQ ); - loads.fill( load / totalCapac ); + var etar; + if ( this.propeller.noProps === 1 ) { - } else if ( load > totalCapac ) { + etar = 0.9922 - 0.05908 * this.propeller.AeAo + 0.07424 * ( this.floatState.Cp - 0.0225 * lcb ); - console.error( "Engines are overloaded. Power plant can't provide current required power." ); - loads.fill( 1 ); + } else if ( this.propeller.noProps === 2 ) { - } + etar = 0.9737 + 0.111 * ( this.floatState.Cp - 0.0225 * lcb ) - 0.06325 * this.propeller.P / this.propeller.D; - // calculate SFOC value for each activated engine - var SFOC; - for ( i = 0; i < loads.length; i ++ ) { + } - if ( loads[ i ] > 0 ) { // if engine is active + var eta = eta0 * this.resistanceState.etah * etar; + var Ps = this.resistanceState.Pe / eta; // W, required brake power - if ( system.engines[ i ].polOrder === 3 ) { + return { eta, Ps, n, Va }; - SFOC = system.engines[ i ].a * Math.pow( loads[ i ], 3 ) + system.engines[ i ].b * Math.pow( loads[ i ], 2 ) + system.engines[ i ].c * loads[ i ] + system.engines[ i ].d; + }, "propulsion" ) +} ); +//@EliasHasle - } else if ( system.engines[ i ].polOrder === 2 ) { +//Depends on Ship and the other core classes. - SFOC = system.engines[ i ].a * Math.pow( loads[ i ], 2 ) + system.engines[ i ].b * loads[ i ] + system.engines[ i ].c; +/* +Handy function for letting the user load a ship design from a local file. (Based on Elias Hasles browseFile function.) - } +Typical usage: +Click here +where useShip takes the loaded ship design as a parameter adn does something with it. - cons += SFOC / ( 1000 ) * loads[ i ] * engCapac[ i ]; // consumption rate in kg/g +According to the ECMAScript standard, it is required that the file browsing is initiated by the user. Google Chrome seems to handle indirect initiation very well, such as having this function in a click handler. +*/ +"use strict"; +var browseShip = function() { + var browseButton; + return function(callback) { + browseButton = document.createElement("input"); + Object.assign(browseButton, { + type: "file", + multiple: false, + style: "display: none", + accept: ".json, application/json", + onchange: function(e) { + //console.log("Change event triggered on browse."); + let file = browseButton.files[0]; + let reader = new FileReader(); + reader.onload = function(event) { + let result = event.target.result; + let specification = JSON.parse(result); + let ship = new Ship(specification); + callback(ship); } - + reader.readAsText(file); } + }); + browseButton.click(); + }; +}();//@EliasHasle - return cons; - - } - - var consumptionRate; - - if ( typeof this.powerPlant.auxiliary === "object" ) { // calculate results for vessels which have main and auxiliary power systems - - // change the propeller states for the maneuvering proppeller - consumptionRate = this.powerPlant.main.noSys * shareLoad( powerPlant.main, Pp / ( 1000 * this.powerPlant.main.noSys ) ); - consumptionRate += shareLoad( this.powerPlant.auxiliary, this.auxPowerState.Paux / 1000 ); +//Depends on Ship and the other core classes. - } else { // calculate results for vessels which have only one power system +/* +Handy function for loading a ship design from file. - consumptionRate = shareLoad( this.powerPlant.main, Pp / 1000 ); +Typical usage: +var myShip; +var filePath = "ships/myShip.json"; +Vessel.loadShip(filePath, function(ship) { + myShip = ship; + doSomething(); +}); - } +*/ - return consumptionRate; +function loadShip( url, callback ) { - } + var request = new XMLHttpRequest(); + request.open( 'GET', url, true ); + request.addEventListener( "load", function ( event ) { + var response = event.target.response; + var specification = JSON.parse( response ); + var ship = new Ship( specification ); + callback( ship ); + } ); + request.send( null ); } +//@EliasHasle -const f = { - linearFromArrays, - bilinear, - bisectionSearch -}; - -if ( typeof window !== "undefined" ) { - - if ( window.__VESSEL__ ) { - - console.warn( "WARNING: Multiple instances of Vessel.js being imported." ); - - } else { - - window.__VESSEL__ = REVISION; - - } +//Very simple download of the specification of a given ship design. Depends on a working getSpecification method. +function downloadShip(ship) { + let specification = ship.getSpecification(); + let output = JSON.stringify(specification); + let link = document.createElement("a"); + link.href = "data:application/json," + encodeURI(output); + link.download = "shipdesignspecification.json"; + link.target = "_blank"; + link.click(); } - -export { BaseObject, DerivedObject, FuelConsumption, HullResistance, Manoeuver, Positioning, PropellerInteraction, Ship, ShipState, StateModule, WaveCreator, WaveMotion, downloadShip, f, loadShip }; +Object.assign(vessel, { + /*JSONSpecObject: JSONSpecObject,*/ + Ship: Ship, + Structure: Structure, + Hull: Hull, + BaseObject: BaseObject, + DerivedObject: DerivedObject, + ShipState: ShipState, + StateModule: StateModule, + WaveCreator: WaveCreator, + WaveMotion: WaveMotion, + Positioning: Positioning, + FuelConsumption: FuelConsumption, + HullResistance: HullResistance, + Manoeuvring: Manoeuvring, + PropellerInteraction: PropellerInteraction, + browseShip: browseShip, + loadShip: loadShip, + downloadShip: downloadShip, + f: { + linearFromArrays: linearFromArrays, + bilinear: bilinear, + bisectionSearch + }, + Vectors: Vectors +}); +})(); diff --git a/build/vessel.module.js b/build/vessel.module.js index 42fda36..3e9a2b7 100644 --- a/build/vessel.module.js +++ b/build/vessel.module.js @@ -3330,8 +3330,6 @@ class PropellerInteraction extends StateModule { function downloadShip( ship ) { - // TODO: Add the possibility to choose the json name and return the link metadata. - let specification = ship.getSpecification(); let output = JSON.stringify( specification ); let link = document.createElement( "a" ); diff --git a/build/vessel.zip b/build/vessel.zip deleted file mode 100644 index 222cb86fee5451399730ab7d0ee4ccf0c7ba84ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44313 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 B%gq1) diff --git a/examples/libs/bootstrap.bundle.min.js b/examples/libs/bootstrap.bundle.min.js index 04e9185..7d50e87 100644 --- a/examples/libs/bootstrap.bundle.min.js +++ b/examples/libs/bootstrap.bundle.min.js @@ -1,7 +1,7 @@ /*! - * Bootstrap v5.3.3 (https://getbootstrap.com/) - * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * Bootstrap v4.0.0 (https://getbootstrap.com) + * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t=new Map,e={set(e,i,n){t.has(e)||t.set(e,new Map);const s=t.get(e);s.has(i)||0===s.size?s.set(i,n):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`)},get:(e,i)=>t.has(e)&&t.get(e).get(i)||null,remove(e,i){if(!t.has(e))return;const n=t.get(e);n.delete(i),0===n.size&&t.delete(e)}},i="transitionend",n=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),s=t=>{t.dispatchEvent(new Event(i))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(n(t)):null,a=t=>{if(!o(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},l=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),c=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?c(t.parentNode):null},h=()=>{},d=t=>{t.offsetHeight},u=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,f=[],p=()=>"rtl"===document.documentElement.dir,m=t=>{var e;e=()=>{const e=u();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(f.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of f)t()})),f.push(e)):e()},g=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,_=(t,e,n=!0)=>{if(!n)return void g(t);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let r=!1;const a=({target:n})=>{n===e&&(r=!0,e.removeEventListener(i,a),g(t))};e.addEventListener(i,a),setTimeout((()=>{r||s(e)}),o)},b=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},v=/[^.]*(?=\..*)\.|.*/,y=/\..*/,w=/::\d+$/,A={};let E=1;const T={mouseenter:"mouseover",mouseleave:"mouseout"},C=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function O(t,e){return e&&`${e}::${E++}`||t.uidEvent||E++}function x(t){const e=O(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function k(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function L(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=I(t);return C.has(o)||(o=t),[n,s,o]}function S(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=L(e,i,n);if(e in T){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=x(t),c=l[a]||(l[a]={}),h=k(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=O(r,e.replace(v,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return P(s,{delegateTarget:r}),n.oneOff&&N.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return P(n,{delegateTarget:t}),i.oneOff&&N.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function D(t,e,i,n,s){const o=k(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function $(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&D(t,e,i,r.callable,r.delegationSelector)}function I(t){return t=t.replace(y,""),T[t]||t}const N={on(t,e,i,n){S(t,e,i,n,!1)},one(t,e,i,n){S(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=L(e,i,n),a=r!==e,l=x(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))$(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(w,"");a&&!e.includes(s)||D(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;D(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=u();let s=null,o=!0,r=!0,a=!1;e!==I(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=P(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function P(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function j(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function M(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const F={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${M(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${M(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=j(t.dataset[n])}return e},getDataAttribute:(t,e)=>j(t.getAttribute(`data-bs-${M(e)}`))};class H{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=o(e)?F.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...o(e)?F.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[n,s]of Object.entries(e)){const e=t[n],r=o(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(r))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${r}" but expected type "${s}".`)}var i}}class W extends H{constructor(t,i){super(),(t=r(t))&&(this._element=t,this._config=this._getConfig(i),e.set(this._element,this.constructor.DATA_KEY,this))}dispose(){e.remove(this._element,this.constructor.DATA_KEY),N.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){_(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return e.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.3"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const B=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e?e.split(",").map((t=>n(t))).join(","):null},z={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!l(t)&&a(t)))},getSelectorFromElement(t){const e=B(t);return e&&z.findOne(e)?e:null},getElementFromSelector(t){const e=B(t);return e?z.findOne(e):null},getMultipleElementsFromSelector(t){const e=B(t);return e?z.find(e):[]}},R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;N.on(document,i,`[data-bs-dismiss="${n}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),l(this))return;const s=z.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()}))},q=".bs.alert",V=`close${q}`,K=`closed${q}`;class Q extends W{static get NAME(){return"alert"}close(){if(N.trigger(this._element,V).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),N.trigger(this._element,K),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=Q.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(Q,"close"),m(Q);const X='[data-bs-toggle="button"]';class Y extends W{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=Y.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}N.on(document,"click.bs.button.data-api",X,(t=>{t.preventDefault();const e=t.target.closest(X);Y.getOrCreateInstance(e).toggle()})),m(Y);const U=".bs.swipe",G=`touchstart${U}`,J=`touchmove${U}`,Z=`touchend${U}`,tt=`pointerdown${U}`,et=`pointerup${U}`,it={endCallback:null,leftCallback:null,rightCallback:null},nt={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class st extends H{constructor(t,e){super(),this._element=t,t&&st.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return it}static get DefaultType(){return nt}static get NAME(){return"swipe"}dispose(){N.off(this._element,U)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),g(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&g(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(N.on(this._element,tt,(t=>this._start(t))),N.on(this._element,et,(t=>this._end(t))),this._element.classList.add("pointer-event")):(N.on(this._element,G,(t=>this._start(t))),N.on(this._element,J,(t=>this._move(t))),N.on(this._element,Z,(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const ot=".bs.carousel",rt=".data-api",at="next",lt="prev",ct="left",ht="right",dt=`slide${ot}`,ut=`slid${ot}`,ft=`keydown${ot}`,pt=`mouseenter${ot}`,mt=`mouseleave${ot}`,gt=`dragstart${ot}`,_t=`load${ot}${rt}`,bt=`click${ot}${rt}`,vt="carousel",yt="active",wt=".active",At=".carousel-item",Et=wt+At,Tt={ArrowLeft:ht,ArrowRight:ct},Ct={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},Ot={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class xt extends W{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=z.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===vt&&this.cycle()}static get Default(){return Ct}static get DefaultType(){return Ot}static get NAME(){return"carousel"}next(){this._slide(at)}nextWhenVisible(){!document.hidden&&a(this._element)&&this.next()}prev(){this._slide(lt)}pause(){this._isSliding&&s(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?N.one(this._element,ut,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void N.one(this._element,ut,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?at:lt;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&N.on(this._element,ft,(t=>this._keydown(t))),"hover"===this._config.pause&&(N.on(this._element,pt,(()=>this.pause())),N.on(this._element,mt,(()=>this._maybeEnableCycle()))),this._config.touch&&st.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of z.find(".carousel-item img",this._element))N.on(t,gt,(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(ct)),rightCallback:()=>this._slide(this._directionToOrder(ht)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new st(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=Tt[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=z.findOne(wt,this._indicatorsElement);e.classList.remove(yt),e.removeAttribute("aria-current");const i=z.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(yt),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===at,s=e||b(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>N.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(dt).defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),d(s),i.classList.add(l),s.classList.add(l),this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(yt),i.classList.remove(yt,c,l),this._isSliding=!1,r(ut)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return z.findOne(Et,this._element)}_getItems(){return z.find(At,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return p()?t===ct?lt:at:t===ct?at:lt}_orderToDirection(t){return p()?t===lt?ct:ht:t===lt?ht:ct}static jQueryInterface(t){return this.each((function(){const e=xt.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}N.on(document,bt,"[data-bs-slide], [data-bs-slide-to]",(function(t){const e=z.getElementFromSelector(this);if(!e||!e.classList.contains(vt))return;t.preventDefault();const i=xt.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===F.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),N.on(window,_t,(()=>{const t=z.find('[data-bs-ride="carousel"]');for(const e of t)xt.getOrCreateInstance(e)})),m(xt);const kt=".bs.collapse",Lt=`show${kt}`,St=`shown${kt}`,Dt=`hide${kt}`,$t=`hidden${kt}`,It=`click${kt}.data-api`,Nt="show",Pt="collapse",jt="collapsing",Mt=`:scope .${Pt} .${Pt}`,Ft='[data-bs-toggle="collapse"]',Ht={parent:null,toggle:!0},Wt={parent:"(null|element)",toggle:"boolean"};class Bt extends W{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=z.find(Ft);for(const t of i){const e=z.getSelectorFromElement(t),i=z.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return Ht}static get DefaultType(){return Wt}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>Bt.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(N.trigger(this._element,Lt).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(Pt),this._element.classList.add(jt),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(jt),this._element.classList.add(Pt,Nt),this._element.style[e]="",N.trigger(this._element,St)}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(N.trigger(this._element,Dt).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,d(this._element),this._element.classList.add(jt),this._element.classList.remove(Pt,Nt);for(const t of this._triggerArray){const e=z.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(jt),this._element.classList.add(Pt),N.trigger(this._element,$t)}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(Nt)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=r(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(Ft);for(const e of t){const t=z.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=z.find(Mt,this._config.parent);return z.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=Bt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}N.on(document,It,Ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of z.getMultipleElementsFromSelector(this))Bt.getOrCreateInstance(t,{toggle:!1}).toggle()})),m(Bt);var zt="top",Rt="bottom",qt="right",Vt="left",Kt="auto",Qt=[zt,Rt,qt,Vt],Xt="start",Yt="end",Ut="clippingParents",Gt="viewport",Jt="popper",Zt="reference",te=Qt.reduce((function(t,e){return t.concat([e+"-"+Xt,e+"-"+Yt])}),[]),ee=[].concat(Qt,[Kt]).reduce((function(t,e){return t.concat([e,e+"-"+Xt,e+"-"+Yt])}),[]),ie="beforeRead",ne="read",se="afterRead",oe="beforeMain",re="main",ae="afterMain",le="beforeWrite",ce="write",he="afterWrite",de=[ie,ne,se,oe,re,ae,le,ce,he];function ue(t){return t?(t.nodeName||"").toLowerCase():null}function fe(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function pe(t){return t instanceof fe(t).Element||t instanceof Element}function me(t){return t instanceof fe(t).HTMLElement||t instanceof HTMLElement}function ge(t){return"undefined"!=typeof ShadowRoot&&(t instanceof fe(t).ShadowRoot||t instanceof ShadowRoot)}const _e={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];me(s)&&ue(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});me(n)&&ue(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function be(t){return t.split("-")[0]}var ve=Math.max,ye=Math.min,we=Math.round;function Ae(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function Ee(){return!/^((?!chrome|android).)*safari/i.test(Ae())}function Te(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&me(t)&&(s=t.offsetWidth>0&&we(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&we(n.height)/t.offsetHeight||1);var r=(pe(t)?fe(t):window).visualViewport,a=!Ee()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function Ce(t){var e=Te(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Oe(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&ge(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function xe(t){return fe(t).getComputedStyle(t)}function ke(t){return["table","td","th"].indexOf(ue(t))>=0}function Le(t){return((pe(t)?t.ownerDocument:t.document)||window.document).documentElement}function Se(t){return"html"===ue(t)?t:t.assignedSlot||t.parentNode||(ge(t)?t.host:null)||Le(t)}function De(t){return me(t)&&"fixed"!==xe(t).position?t.offsetParent:null}function $e(t){for(var e=fe(t),i=De(t);i&&ke(i)&&"static"===xe(i).position;)i=De(i);return i&&("html"===ue(i)||"body"===ue(i)&&"static"===xe(i).position)?e:i||function(t){var e=/firefox/i.test(Ae());if(/Trident/i.test(Ae())&&me(t)&&"fixed"===xe(t).position)return null;var i=Se(t);for(ge(i)&&(i=i.host);me(i)&&["html","body"].indexOf(ue(i))<0;){var n=xe(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Ie(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function Ne(t,e,i){return ve(t,ye(e,i))}function Pe(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function je(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const Me={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=be(i.placement),l=Ie(a),c=[Vt,qt].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return Pe("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:je(t,Qt))}(s.padding,i),d=Ce(o),u="y"===l?zt:Vt,f="y"===l?Rt:qt,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=$e(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,A=Ne(v,w,y),E=l;i.modifiersData[n]=((e={})[E]=A,e.centerOffset=A-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Oe(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Fe(t){return t.split("-")[1]}var He={top:"auto",right:"auto",bottom:"auto",left:"auto"};function We(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=t.isFixed,u=r.x,f=void 0===u?0:u,p=r.y,m=void 0===p?0:p,g="function"==typeof h?h({x:f,y:m}):{x:f,y:m};f=g.x,m=g.y;var _=r.hasOwnProperty("x"),b=r.hasOwnProperty("y"),v=Vt,y=zt,w=window;if(c){var A=$e(i),E="clientHeight",T="clientWidth";A===fe(i)&&"static"!==xe(A=Le(i)).position&&"absolute"===a&&(E="scrollHeight",T="scrollWidth"),(s===zt||(s===Vt||s===qt)&&o===Yt)&&(y=Rt,m-=(d&&A===w&&w.visualViewport?w.visualViewport.height:A[E])-n.height,m*=l?1:-1),s!==Vt&&(s!==zt&&s!==Rt||o!==Yt)||(v=qt,f-=(d&&A===w&&w.visualViewport?w.visualViewport.width:A[T])-n.width,f*=l?1:-1)}var C,O=Object.assign({position:a},c&&He),x=!0===h?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:we(i*s)/s||0,y:we(n*s)/s||0}}({x:f,y:m},fe(i)):{x:f,y:m};return f=x.x,m=x.y,l?Object.assign({},O,((C={})[y]=b?"0":"",C[v]=_?"0":"",C.transform=(w.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",C)):Object.assign({},O,((e={})[y]=b?m+"px":"",e[v]=_?f+"px":"",e.transform="",e))}const Be={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:be(e.placement),variation:Fe(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,We(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,We(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var ze={passive:!0};const Re={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=fe(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,ze)})),a&&l.addEventListener("resize",i.update,ze),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,ze)})),a&&l.removeEventListener("resize",i.update,ze)}},data:{}};var qe={left:"right",right:"left",bottom:"top",top:"bottom"};function Ve(t){return t.replace(/left|right|bottom|top/g,(function(t){return qe[t]}))}var Ke={start:"end",end:"start"};function Qe(t){return t.replace(/start|end/g,(function(t){return Ke[t]}))}function Xe(t){var e=fe(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function Ye(t){return Te(Le(t)).left+Xe(t).scrollLeft}function Ue(t){var e=xe(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ge(t){return["html","body","#document"].indexOf(ue(t))>=0?t.ownerDocument.body:me(t)&&Ue(t)?t:Ge(Se(t))}function Je(t,e){var i;void 0===e&&(e=[]);var n=Ge(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=fe(n),r=s?[o].concat(o.visualViewport||[],Ue(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Je(Se(r)))}function Ze(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function ti(t,e,i){return e===Gt?Ze(function(t,e){var i=fe(t),n=Le(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=Ee();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+Ye(t),y:l}}(t,i)):pe(e)?function(t,e){var i=Te(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):Ze(function(t){var e,i=Le(t),n=Xe(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ve(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ve(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+Ye(t),l=-n.scrollTop;return"rtl"===xe(s||i).direction&&(a+=ve(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Le(t)))}function ei(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?be(s):null,r=s?Fe(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case zt:e={x:a,y:i.y-n.height};break;case Rt:e={x:a,y:i.y+i.height};break;case qt:e={x:i.x+i.width,y:l};break;case Vt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?Ie(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case Xt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Yt:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ii(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.strategy,r=void 0===o?t.strategy:o,a=i.boundary,l=void 0===a?Ut:a,c=i.rootBoundary,h=void 0===c?Gt:c,d=i.elementContext,u=void 0===d?Jt:d,f=i.altBoundary,p=void 0!==f&&f,m=i.padding,g=void 0===m?0:m,_=Pe("number"!=typeof g?g:je(g,Qt)),b=u===Jt?Zt:Jt,v=t.rects.popper,y=t.elements[p?b:u],w=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=Je(Se(t)),i=["absolute","fixed"].indexOf(xe(t).position)>=0&&me(t)?$e(t):t;return pe(i)?e.filter((function(t){return pe(t)&&Oe(t,i)&&"body"!==ue(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=ti(t,i,n);return e.top=ve(s.top,e.top),e.right=ye(s.right,e.right),e.bottom=ye(s.bottom,e.bottom),e.left=ve(s.left,e.left),e}),ti(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(pe(y)?y:y.contextElement||Le(t.elements.popper),l,h,r),A=Te(t.elements.reference),E=ei({reference:A,element:v,strategy:"absolute",placement:s}),T=Ze(Object.assign({},v,E)),C=u===Jt?T:A,O={top:w.top-C.top+_.top,bottom:C.bottom-w.bottom+_.bottom,left:w.left-C.left+_.left,right:C.right-w.right+_.right},x=t.modifiersData.offset;if(u===Jt&&x){var k=x[s];Object.keys(O).forEach((function(t){var e=[qt,Rt].indexOf(t)>=0?1:-1,i=[zt,Rt].indexOf(t)>=0?"y":"x";O[t]+=k[i]*e}))}return O}function ni(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?ee:l,h=Fe(n),d=h?a?te:te.filter((function(t){return Fe(t)===h})):Qt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ii(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[be(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const si={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=be(g),b=l||(_!==g&&p?function(t){if(be(t)===Kt)return[];var e=Ve(t);return[Qe(t),e,Qe(e)]}(g):[Ve(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(be(i)===Kt?ni(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,A=new Map,E=!0,T=v[0],C=0;C=0,S=L?"width":"height",D=ii(e,{placement:O,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),$=L?k?qt:Vt:k?Rt:zt;y[S]>w[S]&&($=Ve($));var I=Ve($),N=[];if(o&&N.push(D[x]<=0),a&&N.push(D[$]<=0,D[I]<=0),N.every((function(t){return t}))){T=O,E=!1;break}A.set(O,N)}if(E)for(var P=function(t){var e=v.find((function(e){var i=A.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},j=p?3:1;j>0&&"break"!==P(j);j--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function oi(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function ri(t){return[zt,qt,Rt,Vt].some((function(e){return t[e]>=0}))}const ai={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ii(e,{elementContext:"reference"}),a=ii(e,{altBoundary:!0}),l=oi(r,n),c=oi(a,s,o),h=ri(l),d=ri(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},li={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=ee.reduce((function(t,i){return t[i]=function(t,e,i){var n=be(t),s=[Vt,zt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[Vt,qt].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},ci={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=ei({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},hi={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ii(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=be(e.placement),b=Fe(e.placement),v=!b,y=Ie(_),w="x"===y?"y":"x",A=e.modifiersData.popperOffsets,E=e.rects.reference,T=e.rects.popper,C="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,O="number"==typeof C?{mainAxis:C,altAxis:C}:Object.assign({mainAxis:0,altAxis:0},C),x=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,k={x:0,y:0};if(A){if(o){var L,S="y"===y?zt:Vt,D="y"===y?Rt:qt,$="y"===y?"height":"width",I=A[y],N=I+g[S],P=I-g[D],j=f?-T[$]/2:0,M=b===Xt?E[$]:T[$],F=b===Xt?-T[$]:-E[$],H=e.elements.arrow,W=f&&H?Ce(H):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},z=B[S],R=B[D],q=Ne(0,E[$],W[$]),V=v?E[$]/2-j-q-z-O.mainAxis:M-q-z-O.mainAxis,K=v?-E[$]/2+j+q+R+O.mainAxis:F+q+R+O.mainAxis,Q=e.elements.arrow&&$e(e.elements.arrow),X=Q?"y"===y?Q.clientTop||0:Q.clientLeft||0:0,Y=null!=(L=null==x?void 0:x[y])?L:0,U=I+K-Y,G=Ne(f?ye(N,I+V-Y-X):N,I,f?ve(P,U):P);A[y]=G,k[y]=G-I}if(a){var J,Z="x"===y?zt:Vt,tt="x"===y?Rt:qt,et=A[w],it="y"===w?"height":"width",nt=et+g[Z],st=et-g[tt],ot=-1!==[zt,Vt].indexOf(_),rt=null!=(J=null==x?void 0:x[w])?J:0,at=ot?nt:et-E[it]-T[it]-rt+O.altAxis,lt=ot?et+E[it]+T[it]-rt-O.altAxis:st,ct=f&&ot?function(t,e,i){var n=Ne(t,e,i);return n>i?i:n}(at,et,lt):Ne(f?at:nt,et,f?lt:st);A[w]=ct,k[w]=ct-et}e.modifiersData[n]=k}},requiresIfExists:["offset"]};function di(t,e,i){void 0===i&&(i=!1);var n,s,o=me(e),r=me(e)&&function(t){var e=t.getBoundingClientRect(),i=we(e.width)/t.offsetWidth||1,n=we(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=Le(e),l=Te(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==ue(e)||Ue(a))&&(c=(n=e)!==fe(n)&&me(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:Xe(n)),me(e)?((h=Te(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=Ye(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function ui(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var fi={placement:"bottom",modifiers:[],strategy:"absolute"};function pi(){for(var t=arguments.length,e=new Array(t),i=0;iNumber.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(F.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...g(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=z.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>a(t)));i.length&&b(i,e,t===Ti,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=qi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=z.find(Ni);for(const i of e){const e=qi.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Ei,Ti].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(Ii)?this:z.prev(this,Ii)[0]||z.next(this,Ii)[0]||z.findOne(Ii,t.delegateTarget.parentNode),o=qi.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}N.on(document,Si,Ii,qi.dataApiKeydownHandler),N.on(document,Si,Pi,qi.dataApiKeydownHandler),N.on(document,Li,qi.clearMenus),N.on(document,Di,qi.clearMenus),N.on(document,Li,Ii,(function(t){t.preventDefault(),qi.getOrCreateInstance(this).toggle()})),m(qi);const Vi="backdrop",Ki="show",Qi=`mousedown.bs.${Vi}`,Xi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Yi={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Ui extends H{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Xi}static get DefaultType(){return Yi}static get NAME(){return Vi}show(t){if(!this._config.isVisible)return void g(t);this._append();const e=this._getElement();this._config.isAnimated&&d(e),e.classList.add(Ki),this._emulateAnimation((()=>{g(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(Ki),this._emulateAnimation((()=>{this.dispose(),g(t)}))):g(t)}dispose(){this._isAppended&&(N.off(this._element,Qi),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=r(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),N.on(t,Qi,(()=>{g(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){_(t,this._getElement(),this._config.isAnimated)}}const Gi=".bs.focustrap",Ji=`focusin${Gi}`,Zi=`keydown.tab${Gi}`,tn="backward",en={autofocus:!0,trapElement:null},nn={autofocus:"boolean",trapElement:"element"};class sn extends H{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return en}static get DefaultType(){return nn}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),N.off(document,Gi),N.on(document,Ji,(t=>this._handleFocusin(t))),N.on(document,Zi,(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,N.off(document,Gi))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=z.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===tn?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?tn:"forward")}}const on=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",rn=".sticky-top",an="padding-right",ln="margin-right";class cn{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,an,(e=>e+t)),this._setElementAttributes(on,an,(e=>e+t)),this._setElementAttributes(rn,ln,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,an),this._resetElementAttributes(on,an),this._resetElementAttributes(rn,ln)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&F.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=F.getDataAttribute(t,e);null!==i?(F.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(o(t))e(t);else for(const i of z.find(t,this._element))e(i)}}const hn=".bs.modal",dn=`hide${hn}`,un=`hidePrevented${hn}`,fn=`hidden${hn}`,pn=`show${hn}`,mn=`shown${hn}`,gn=`resize${hn}`,_n=`click.dismiss${hn}`,bn=`mousedown.dismiss${hn}`,vn=`keydown.dismiss${hn}`,yn=`click${hn}.data-api`,wn="modal-open",An="show",En="modal-static",Tn={backdrop:!0,focus:!0,keyboard:!0},Cn={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class On extends W{constructor(t,e){super(t,e),this._dialog=z.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new cn,this._addEventListeners()}static get Default(){return Tn}static get DefaultType(){return Cn}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||N.trigger(this._element,pn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(wn),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(N.trigger(this._element,dn).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(An),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){N.off(window,hn),N.off(this._dialog,hn),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Ui({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new sn({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=z.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),d(this._element),this._element.classList.add(An),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,N.trigger(this._element,mn,{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){N.on(this._element,vn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),N.on(window,gn,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),N.on(this._element,bn,(t=>{N.one(this._element,_n,(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(wn),this._resetAdjustments(),this._scrollBar.reset(),N.trigger(this._element,fn)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(N.trigger(this._element,un).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(En)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(En),this._queueCallback((()=>{this._element.classList.remove(En),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=p()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=p()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=On.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}N.on(document,yn,'[data-bs-toggle="modal"]',(function(t){const e=z.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),N.one(e,pn,(t=>{t.defaultPrevented||N.one(e,fn,(()=>{a(this)&&this.focus()}))}));const i=z.findOne(".modal.show");i&&On.getInstance(i).hide(),On.getOrCreateInstance(e).toggle(this)})),R(On),m(On);const xn=".bs.offcanvas",kn=".data-api",Ln=`load${xn}${kn}`,Sn="show",Dn="showing",$n="hiding",In=".offcanvas.show",Nn=`show${xn}`,Pn=`shown${xn}`,jn=`hide${xn}`,Mn=`hidePrevented${xn}`,Fn=`hidden${xn}`,Hn=`resize${xn}`,Wn=`click${xn}${kn}`,Bn=`keydown.dismiss${xn}`,zn={backdrop:!0,keyboard:!0,scroll:!1},Rn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class qn extends W{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return zn}static get DefaultType(){return Rn}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||N.trigger(this._element,Nn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new cn).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Dn),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(Sn),this._element.classList.remove(Dn),N.trigger(this._element,Pn,{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(N.trigger(this._element,jn).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add($n),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(Sn,$n),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new cn).reset(),N.trigger(this._element,Fn)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Ui({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():N.trigger(this._element,Mn)}:null})}_initializeFocusTrap(){return new sn({trapElement:this._element})}_addEventListeners(){N.on(this._element,Bn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():N.trigger(this._element,Mn))}))}static jQueryInterface(t){return this.each((function(){const e=qn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}N.on(document,Wn,'[data-bs-toggle="offcanvas"]',(function(t){const e=z.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this))return;N.one(e,Fn,(()=>{a(this)&&this.focus()}));const i=z.findOne(In);i&&i!==e&&qn.getInstance(i).hide(),qn.getOrCreateInstance(e).toggle(this)})),N.on(window,Ln,(()=>{for(const t of z.find(In))qn.getOrCreateInstance(t).show()})),N.on(window,Hn,(()=>{for(const t of z.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&qn.getOrCreateInstance(t).hide()})),R(qn),m(qn);const Vn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],dd:[],div:[],dl:[],dt:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Kn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Qn=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Xn=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!Kn.has(i)||Boolean(Qn.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},Yn={allowList:Vn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},Un={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Gn={entry:"(string|element|function|null)",selector:"(string|element)"};class Jn extends H{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Yn}static get DefaultType(){return Un}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},Gn)}_setContent(t,e,i){const n=z.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?o(e)?this._putElementInTemplate(r(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Xn(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return g(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const Zn=new Set(["sanitize","allowList","sanitizeFn"]),ts="fade",es="show",is=".modal",ns="hide.bs.modal",ss="hover",os="focus",rs={AUTO:"auto",TOP:"top",RIGHT:p()?"left":"right",BOTTOM:"bottom",LEFT:p()?"right":"left"},as={allowList:Vn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},ls={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class cs extends W{constructor(t,e){if(void 0===vi)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return as}static get DefaultType(){return ls}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),N.off(this._element.closest(is),ns,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=N.trigger(this._element,this.constructor.eventName("show")),e=(c(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),N.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(es),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.on(t,"mouseover",h);this._queueCallback((()=>{N.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(this._isShown()&&!N.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(es),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.off(t,"mouseover",h);this._activeTrigger.click=!1,this._activeTrigger[os]=!1,this._activeTrigger[ss]=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),N.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(ts,es),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(ts),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Jn({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(ts)}_isShown(){return this.tip&&this.tip.classList.contains(es)}_createPopper(t){const e=g(this._config.placement,[this,t,this._element]),i=rs[e.toUpperCase()];return bi(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return g(t,[this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...g(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)N.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===ss?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===ss?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");N.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?os:ss]=!0,e._enter()})),N.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?os:ss]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},N.on(this._element.closest(is),ns,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=F.getDataAttributes(this._element);for(const t of Object.keys(e))Zn.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=cs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(cs);const hs={...cs.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},ds={...cs.DefaultType,content:"(null|string|element|function)"};class us extends cs{static get Default(){return hs}static get DefaultType(){return ds}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=us.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(us);const fs=".bs.scrollspy",ps=`activate${fs}`,ms=`click${fs}`,gs=`load${fs}.data-api`,_s="active",bs="[href]",vs=".nav-link",ys=`${vs}, .nav-item > ${vs}, .list-group-item`,ws={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},As={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Es extends W{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return ws}static get DefaultType(){return As}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=r(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(N.off(this._config.target,ms),N.on(this._config.target,ms,bs,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=z.find(bs,this._config.target);for(const e of t){if(!e.hash||l(e))continue;const t=z.findOne(decodeURI(e.hash),this._element);a(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(_s),this._activateParents(t),N.trigger(this._element,ps,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))z.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(_s);else for(const e of z.parents(t,".nav, .list-group"))for(const t of z.prev(e,ys))t.classList.add(_s)}_clearActiveClass(t){t.classList.remove(_s);const e=z.find(`${bs}.${_s}`,t);for(const t of e)t.classList.remove(_s)}static jQueryInterface(t){return this.each((function(){const e=Es.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(window,gs,(()=>{for(const t of z.find('[data-bs-spy="scroll"]'))Es.getOrCreateInstance(t)})),m(Es);const Ts=".bs.tab",Cs=`hide${Ts}`,Os=`hidden${Ts}`,xs=`show${Ts}`,ks=`shown${Ts}`,Ls=`click${Ts}`,Ss=`keydown${Ts}`,Ds=`load${Ts}`,$s="ArrowLeft",Is="ArrowRight",Ns="ArrowUp",Ps="ArrowDown",js="Home",Ms="End",Fs="active",Hs="fade",Ws="show",Bs=".dropdown-toggle",zs=`:not(${Bs})`,Rs='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',qs=`.nav-link${zs}, .list-group-item${zs}, [role="tab"]${zs}, ${Rs}`,Vs=`.${Fs}[data-bs-toggle="tab"], .${Fs}[data-bs-toggle="pill"], .${Fs}[data-bs-toggle="list"]`;class Ks extends W{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),N.on(this._element,Ss,(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?N.trigger(e,Cs,{relatedTarget:t}):null;N.trigger(t,xs,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(Fs),this._activate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),N.trigger(t,ks,{relatedTarget:e})):t.classList.add(Ws)}),t,t.classList.contains(Hs)))}_deactivate(t,e){t&&(t.classList.remove(Fs),t.blur(),this._deactivate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),N.trigger(t,Os,{relatedTarget:e})):t.classList.remove(Ws)}),t,t.classList.contains(Hs)))}_keydown(t){if(![$s,Is,Ns,Ps,js,Ms].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter((t=>!l(t)));let i;if([js,Ms].includes(t.key))i=e[t.key===js?0:e.length-1];else{const n=[Is,Ps].includes(t.key);i=b(e,t.target,n,!0)}i&&(i.focus({preventScroll:!0}),Ks.getOrCreateInstance(i).show())}_getChildren(){return z.find(qs,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=z.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=z.findOne(t,i);s&&s.classList.toggle(n,e)};n(Bs,Fs),n(".dropdown-menu",Ws),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(Fs)}_getInnerElement(t){return t.matches(qs)?t:z.findOne(qs,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=Ks.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(document,Ls,Rs,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this)||Ks.getOrCreateInstance(this).show()})),N.on(window,Ds,(()=>{for(const t of z.find(Vs))Ks.getOrCreateInstance(t)})),m(Ks);const Qs=".bs.toast",Xs=`mouseover${Qs}`,Ys=`mouseout${Qs}`,Us=`focusin${Qs}`,Gs=`focusout${Qs}`,Js=`hide${Qs}`,Zs=`hidden${Qs}`,to=`show${Qs}`,eo=`shown${Qs}`,io="hide",no="show",so="showing",oo={animation:"boolean",autohide:"boolean",delay:"number"},ro={animation:!0,autohide:!0,delay:5e3};class ao extends W{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return ro}static get DefaultType(){return oo}static get NAME(){return"toast"}show(){N.trigger(this._element,to).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(io),d(this._element),this._element.classList.add(no,so),this._queueCallback((()=>{this._element.classList.remove(so),N.trigger(this._element,eo),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(N.trigger(this._element,Js).defaultPrevented||(this._element.classList.add(so),this._queueCallback((()=>{this._element.classList.add(io),this._element.classList.remove(so,no),N.trigger(this._element,Zs)}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(no),super.dispose()}isShown(){return this._element.classList.contains(no)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){N.on(this._element,Xs,(t=>this._onInteraction(t,!0))),N.on(this._element,Ys,(t=>this._onInteraction(t,!1))),N.on(this._element,Us,(t=>this._onInteraction(t,!0))),N.on(this._element,Gs,(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=ao.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(ao),m(ao),{Alert:Q,Button:Y,Carousel:xt,Collapse:Bt,Dropdown:qi,Modal:On,Offcanvas:qn,Popover:us,ScrollSpy:Es,Tab:Ks,Toast:ao,Tooltip:cs}})); +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery")):"function"==typeof define&&define.amd?define(["exports","jquery"],e):e(t.bootstrap={},t.jQuery)}(this,function(t,e){"use strict";function n(t,e){for(var n=0;n0?i:null}catch(t){return null}},reflow:function(t){return t.offsetHeight},triggerTransitionEnd:function(n){t(n).trigger(e.end)},supportsTransitionEnd:function(){return Boolean(e)},isElement:function(t){return(t[0]||t).nodeType},typeCheckConfig:function(t,e,n){for(var r in n)if(Object.prototype.hasOwnProperty.call(n,r)){var o=n[r],s=e[r],a=s&&i.isElement(s)?"element":(l=s,{}.toString.call(l).match(/\s([a-zA-Z]+)/)[1].toLowerCase());if(!new RegExp(o).test(a))throw new Error(t.toUpperCase()+': Option "'+r+'" provided type "'+a+'" but expected type "'+o+'".')}var l}};return e=("undefined"==typeof window||!window.QUnit)&&{end:"transitionend"},t.fn.emulateTransitionEnd=n,i.supportsTransitionEnd()&&(t.event.special[i.TRANSITION_END]={bindType:e.end,delegateType:e.end,handle:function(e){if(t(e.target).is(this))return e.handleObj.handler.apply(this,arguments)}}),i}(e=e&&e.hasOwnProperty("default")?e.default:e),L=(s="alert",l="."+(a="bs.alert"),c=(o=e).fn[s],h={CLOSE:"close"+l,CLOSED:"closed"+l,CLICK_DATA_API:"click"+l+".data-api"},f="alert",u="fade",d="show",p=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){t=t||this._element;var e=this._getRootElement(t);this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){o.removeData(this._element,a),this._element=null},e._getRootElement=function(t){var e=k.getSelectorFromElement(t),n=!1;return e&&(n=o(e)[0]),n||(n=o(t).closest("."+f)[0]),n},e._triggerCloseEvent=function(t){var e=o.Event(h.CLOSE);return o(t).trigger(e),e},e._removeElement=function(t){var e=this;o(t).removeClass(d),k.supportsTransitionEnd()&&o(t).hasClass(u)?o(t).one(k.TRANSITION_END,function(n){return e._destroyElement(t,n)}).emulateTransitionEnd(150):this._destroyElement(t)},e._destroyElement=function(t){o(t).detach().trigger(h.CLOSED).remove()},t._jQueryInterface=function(e){return this.each(function(){var n=o(this),i=n.data(a);i||(i=new t(this),n.data(a,i)),"close"===e&&i[e](this)})},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},i(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),o(document).on(h.CLICK_DATA_API,'[data-dismiss="alert"]',p._handleDismiss(new p)),o.fn[s]=p._jQueryInterface,o.fn[s].Constructor=p,o.fn[s].noConflict=function(){return o.fn[s]=c,p._jQueryInterface},p),P=(m="button",v="."+(_="bs.button"),E=".data-api",y=(g=e).fn[m],b="active",T="btn",C="focus",w='[data-toggle^="button"]',I='[data-toggle="buttons"]',A="input",D=".active",S=".btn",O={CLICK_DATA_API:"click"+v+E,FOCUS_BLUR_DATA_API:"focus"+v+E+" blur"+v+E},N=function(){function t(t){this._element=t}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=g(this._element).closest(I)[0];if(n){var i=g(this._element).find(A)[0];if(i){if("radio"===i.type)if(i.checked&&g(this._element).hasClass(b))t=!1;else{var r=g(n).find(D)[0];r&&g(r).removeClass(b)}if(t){if(i.hasAttribute("disabled")||n.hasAttribute("disabled")||i.classList.contains("disabled")||n.classList.contains("disabled"))return;i.checked=!g(this._element).hasClass(b),g(i).trigger("change")}i.focus(),e=!1}}e&&this._element.setAttribute("aria-pressed",!g(this._element).hasClass(b)),t&&g(this._element).toggleClass(b)},e.dispose=function(){g.removeData(this._element,_),this._element=null},t._jQueryInterface=function(e){return this.each(function(){var n=g(this).data(_);n||(n=new t(this),g(this).data(_,n)),"toggle"===e&&n[e]()})},i(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),g(document).on(O.CLICK_DATA_API,w,function(t){t.preventDefault();var e=t.target;g(e).hasClass(T)||(e=g(e).closest(S)),N._jQueryInterface.call(g(e),"toggle")}).on(O.FOCUS_BLUR_DATA_API,w,function(t){var e=g(t.target).closest(S)[0];g(e).toggleClass(C,/^focus(in)?$/.test(t.type))}),g.fn[m]=N._jQueryInterface,g.fn[m].Constructor=N,g.fn[m].noConflict=function(){return g.fn[m]=y,N._jQueryInterface},N),x=function(t){var e="carousel",n="bs.carousel",o="."+n,s=t.fn[e],a={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0},l={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean"},c="next",h="prev",f="left",u="right",d={SLIDE:"slide"+o,SLID:"slid"+o,KEYDOWN:"keydown"+o,MOUSEENTER:"mouseenter"+o,MOUSELEAVE:"mouseleave"+o,TOUCHEND:"touchend"+o,LOAD_DATA_API:"load"+o+".data-api",CLICK_DATA_API:"click"+o+".data-api"},p="carousel",g="active",m="slide",_="carousel-item-right",v="carousel-item-left",E="carousel-item-next",y="carousel-item-prev",b={ACTIVE:".active",ACTIVE_ITEM:".active.carousel-item",ITEM:".carousel-item",NEXT_PREV:".carousel-item-next, .carousel-item-prev",INDICATORS:".carousel-indicators",DATA_SLIDE:"[data-slide], [data-slide-to]",DATA_RIDE:'[data-ride="carousel"]'},T=function(){function s(e,n){this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this._config=this._getConfig(n),this._element=t(e)[0],this._indicatorsElement=t(this._element).find(b.INDICATORS)[0],this._addEventListeners()}var T=s.prototype;return T.next=function(){this._isSliding||this._slide(c)},T.nextWhenVisible=function(){!document.hidden&&t(this._element).is(":visible")&&"hidden"!==t(this._element).css("visibility")&&this.next()},T.prev=function(){this._isSliding||this._slide(h)},T.pause=function(e){e||(this._isPaused=!0),t(this._element).find(b.NEXT_PREV)[0]&&k.supportsTransitionEnd()&&(k.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},T.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},T.to=function(e){var n=this;this._activeElement=t(this._element).find(b.ACTIVE_ITEM)[0];var i=this._getItemIndex(this._activeElement);if(!(e>this._items.length-1||e<0))if(this._isSliding)t(this._element).one(d.SLID,function(){return n.to(e)});else{if(i===e)return this.pause(),void this.cycle();var r=e>i?c:h;this._slide(r,this._items[e])}},T.dispose=function(){t(this._element).off(o),t.removeData(this._element,n),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},T._getConfig=function(t){return t=r({},a,t),k.typeCheckConfig(e,t,l),t},T._addEventListeners=function(){var e=this;this._config.keyboard&&t(this._element).on(d.KEYDOWN,function(t){return e._keydown(t)}),"hover"===this._config.pause&&(t(this._element).on(d.MOUSEENTER,function(t){return e.pause(t)}).on(d.MOUSELEAVE,function(t){return e.cycle(t)}),"ontouchstart"in document.documentElement&&t(this._element).on(d.TOUCHEND,function(){e.pause(),e.touchTimeout&&clearTimeout(e.touchTimeout),e.touchTimeout=setTimeout(function(t){return e.cycle(t)},500+e._config.interval)}))},T._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},T._getItemIndex=function(e){return this._items=t.makeArray(t(e).parent().find(b.ITEM)),this._items.indexOf(e)},T._getItemByDirection=function(t,e){var n=t===c,i=t===h,r=this._getItemIndex(e),o=this._items.length-1;if((i&&0===r||n&&r===o)&&!this._config.wrap)return e;var s=(r+(t===h?-1:1))%this._items.length;return-1===s?this._items[this._items.length-1]:this._items[s]},T._triggerSlideEvent=function(e,n){var i=this._getItemIndex(e),r=this._getItemIndex(t(this._element).find(b.ACTIVE_ITEM)[0]),o=t.Event(d.SLIDE,{relatedTarget:e,direction:n,from:r,to:i});return t(this._element).trigger(o),o},T._setActiveIndicatorElement=function(e){if(this._indicatorsElement){t(this._indicatorsElement).find(b.ACTIVE).removeClass(g);var n=this._indicatorsElement.children[this._getItemIndex(e)];n&&t(n).addClass(g)}},T._slide=function(e,n){var i,r,o,s=this,a=t(this._element).find(b.ACTIVE_ITEM)[0],l=this._getItemIndex(a),h=n||a&&this._getItemByDirection(e,a),p=this._getItemIndex(h),T=Boolean(this._interval);if(e===c?(i=v,r=E,o=f):(i=_,r=y,o=u),h&&t(h).hasClass(g))this._isSliding=!1;else if(!this._triggerSlideEvent(h,o).isDefaultPrevented()&&a&&h){this._isSliding=!0,T&&this.pause(),this._setActiveIndicatorElement(h);var C=t.Event(d.SLID,{relatedTarget:h,direction:o,from:l,to:p});k.supportsTransitionEnd()&&t(this._element).hasClass(m)?(t(h).addClass(r),k.reflow(h),t(a).addClass(i),t(h).addClass(i),t(a).one(k.TRANSITION_END,function(){t(h).removeClass(i+" "+r).addClass(g),t(a).removeClass(g+" "+r+" "+i),s._isSliding=!1,setTimeout(function(){return t(s._element).trigger(C)},0)}).emulateTransitionEnd(600)):(t(a).removeClass(g),t(h).addClass(g),this._isSliding=!1,t(this._element).trigger(C)),T&&this.cycle()}},s._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),o=r({},a,t(this).data());"object"==typeof e&&(o=r({},o,e));var l="string"==typeof e?e:o.slide;if(i||(i=new s(this,o),t(this).data(n,i)),"number"==typeof e)i.to(e);else if("string"==typeof l){if("undefined"==typeof i[l])throw new TypeError('No method named "'+l+'"');i[l]()}else o.interval&&(i.pause(),i.cycle())})},s._dataApiClickHandler=function(e){var i=k.getSelectorFromElement(this);if(i){var o=t(i)[0];if(o&&t(o).hasClass(p)){var a=r({},t(o).data(),t(this).data()),l=this.getAttribute("data-slide-to");l&&(a.interval=!1),s._jQueryInterface.call(t(o),a),l&&t(o).data(n).to(l),e.preventDefault()}}},i(s,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),s}();return t(document).on(d.CLICK_DATA_API,b.DATA_SLIDE,T._dataApiClickHandler),t(window).on(d.LOAD_DATA_API,function(){t(b.DATA_RIDE).each(function(){var e=t(this);T._jQueryInterface.call(e,e.data())})}),t.fn[e]=T._jQueryInterface,t.fn[e].Constructor=T,t.fn[e].noConflict=function(){return t.fn[e]=s,T._jQueryInterface},T}(e),R=function(t){var e="collapse",n="bs.collapse",o="."+n,s=t.fn[e],a={toggle:!0,parent:""},l={toggle:"boolean",parent:"(string|element)"},c={SHOW:"show"+o,SHOWN:"shown"+o,HIDE:"hide"+o,HIDDEN:"hidden"+o,CLICK_DATA_API:"click"+o+".data-api"},h="show",f="collapse",u="collapsing",d="collapsed",p="width",g="height",m={ACTIVES:".show, .collapsing",DATA_TOGGLE:'[data-toggle="collapse"]'},_=function(){function o(e,n){this._isTransitioning=!1,this._element=e,this._config=this._getConfig(n),this._triggerArray=t.makeArray(t('[data-toggle="collapse"][href="#'+e.id+'"],[data-toggle="collapse"][data-target="#'+e.id+'"]'));for(var i=t(m.DATA_TOGGLE),r=0;r0&&(this._selector=s,this._triggerArray.push(o))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var s=o.prototype;return s.toggle=function(){t(this._element).hasClass(h)?this.hide():this.show()},s.show=function(){var e,i,r=this;if(!this._isTransitioning&&!t(this._element).hasClass(h)&&(this._parent&&0===(e=t.makeArray(t(this._parent).find(m.ACTIVES).filter('[data-parent="'+this._config.parent+'"]'))).length&&(e=null),!(e&&(i=t(e).not(this._selector).data(n))&&i._isTransitioning))){var s=t.Event(c.SHOW);if(t(this._element).trigger(s),!s.isDefaultPrevented()){e&&(o._jQueryInterface.call(t(e).not(this._selector),"hide"),i||t(e).data(n,null));var a=this._getDimension();t(this._element).removeClass(f).addClass(u),this._element.style[a]=0,this._triggerArray.length>0&&t(this._triggerArray).removeClass(d).attr("aria-expanded",!0),this.setTransitioning(!0);var l=function(){t(r._element).removeClass(u).addClass(f).addClass(h),r._element.style[a]="",r.setTransitioning(!1),t(r._element).trigger(c.SHOWN)};if(k.supportsTransitionEnd()){var p="scroll"+(a[0].toUpperCase()+a.slice(1));t(this._element).one(k.TRANSITION_END,l).emulateTransitionEnd(600),this._element.style[a]=this._element[p]+"px"}else l()}}},s.hide=function(){var e=this;if(!this._isTransitioning&&t(this._element).hasClass(h)){var n=t.Event(c.HIDE);if(t(this._element).trigger(n),!n.isDefaultPrevented()){var i=this._getDimension();if(this._element.style[i]=this._element.getBoundingClientRect()[i]+"px",k.reflow(this._element),t(this._element).addClass(u).removeClass(f).removeClass(h),this._triggerArray.length>0)for(var r=0;r0&&t(n).toggleClass(d,!i).attr("aria-expanded",i)}},o._getTargetFromElement=function(e){var n=k.getSelectorFromElement(e);return n?t(n)[0]:null},o._jQueryInterface=function(e){return this.each(function(){var i=t(this),s=i.data(n),l=r({},a,i.data(),"object"==typeof e&&e);if(!s&&l.toggle&&/show|hide/.test(e)&&(l.toggle=!1),s||(s=new o(this,l),i.data(n,s)),"string"==typeof e){if("undefined"==typeof s[e])throw new TypeError('No method named "'+e+'"');s[e]()}})},i(o,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),o}();return t(document).on(c.CLICK_DATA_API,m.DATA_TOGGLE,function(e){"A"===e.currentTarget.tagName&&e.preventDefault();var i=t(this),r=k.getSelectorFromElement(this);t(r).each(function(){var e=t(this),r=e.data(n)?"toggle":i.data();_._jQueryInterface.call(e,r)})}),t.fn[e]=_._jQueryInterface,t.fn[e].Constructor=_,t.fn[e].noConflict=function(){return t.fn[e]=s,_._jQueryInterface},_}(e),j="undefined"!=typeof window&&"undefined"!=typeof document,H=["Edge","Trident","Firefox"],M=0,W=0;W=0){M=1;break}var U=j&&window.Promise?function(t){var e=!1;return function(){e||(e=!0,window.Promise.resolve().then(function(){e=!1,t()}))}}:function(t){var e=!1;return function(){e||(e=!0,setTimeout(function(){e=!1,t()},M))}};function B(t){return t&&"[object Function]"==={}.toString.call(t)}function F(t,e){if(1!==t.nodeType)return[];var n=getComputedStyle(t,null);return e?n[e]:n}function K(t){return"HTML"===t.nodeName?t:t.parentNode||t.host}function V(t){if(!t)return document.body;switch(t.nodeName){case"HTML":case"BODY":return t.ownerDocument.body;case"#document":return t.body}var e=F(t),n=e.overflow,i=e.overflowX,r=e.overflowY;return/(auto|scroll)/.test(n+r+i)?t:V(K(t))}function Q(t){var e=t&&t.offsetParent,n=e&&e.nodeName;return n&&"BODY"!==n&&"HTML"!==n?-1!==["TD","TABLE"].indexOf(e.nodeName)&&"static"===F(e,"position")?Q(e):e:t?t.ownerDocument.documentElement:document.documentElement}function Y(t){return null!==t.parentNode?Y(t.parentNode):t}function G(t,e){if(!(t&&t.nodeType&&e&&e.nodeType))return document.documentElement;var n=t.compareDocumentPosition(e)&Node.DOCUMENT_POSITION_FOLLOWING,i=n?t:e,r=n?e:t,o=document.createRange();o.setStart(i,0),o.setEnd(r,0);var s,a,l=o.commonAncestorContainer;if(t!==l&&e!==l||i.contains(r))return"BODY"===(a=(s=l).nodeName)||"HTML"!==a&&Q(s.firstElementChild)!==s?Q(l):l;var c=Y(t);return c.host?G(c.host,e):G(t,Y(e).host)}function q(t){var e="top"===(arguments.length>1&&void 0!==arguments[1]?arguments[1]:"top")?"scrollTop":"scrollLeft",n=t.nodeName;if("BODY"===n||"HTML"===n){var i=t.ownerDocument.documentElement;return(t.ownerDocument.scrollingElement||i)[e]}return t[e]}function z(t,e){var n="x"===e?"Left":"Top",i="Left"===n?"Right":"Bottom";return parseFloat(t["border"+n+"Width"],10)+parseFloat(t["border"+i+"Width"],10)}var X=void 0,Z=function(){return void 0===X&&(X=-1!==navigator.appVersion.indexOf("MSIE 10")),X};function J(t,e,n,i){return Math.max(e["offset"+t],e["scroll"+t],n["client"+t],n["offset"+t],n["scroll"+t],Z()?n["offset"+t]+i["margin"+("Height"===t?"Top":"Left")]+i["margin"+("Height"===t?"Bottom":"Right")]:0)}function $(){var t=document.body,e=document.documentElement,n=Z()&&getComputedStyle(e);return{height:J("Height",t,e,n),width:J("Width",t,e,n)}}var tt=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},et=function(){function t(t,e){for(var n=0;n2&&void 0!==arguments[2]&&arguments[2],i=q(e,"top"),r=q(e,"left"),o=n?-1:1;return t.top+=i*o,t.bottom+=i*o,t.left+=r*o,t.right+=r*o,t}(h,e)),h}function at(t,e,n,i){var r,o,s,a,l,c,h,f={top:0,left:0},u=G(t,e);if("viewport"===i)o=(r=u).ownerDocument.documentElement,s=st(r,o),a=Math.max(o.clientWidth,window.innerWidth||0),l=Math.max(o.clientHeight,window.innerHeight||0),c=q(o),h=q(o,"left"),f=rt({top:c-s.top+s.marginTop,left:h-s.left+s.marginLeft,width:a,height:l});else{var d=void 0;"scrollParent"===i?"BODY"===(d=V(K(e))).nodeName&&(d=t.ownerDocument.documentElement):d="window"===i?t.ownerDocument.documentElement:i;var p=st(d,u);if("HTML"!==d.nodeName||function t(e){var n=e.nodeName;return"BODY"!==n&&"HTML"!==n&&("fixed"===F(e,"position")||t(K(e)))}(u))f=p;else{var g=$(),m=g.height,_=g.width;f.top+=p.top-p.marginTop,f.bottom=m+p.top,f.left+=p.left-p.marginLeft,f.right=_+p.left}}return f.left+=n,f.top+=n,f.right-=n,f.bottom-=n,f}function lt(t,e,n,i,r){var o=arguments.length>5&&void 0!==arguments[5]?arguments[5]:0;if(-1===t.indexOf("auto"))return t;var s=at(n,i,o,r),a={top:{width:s.width,height:e.top-s.top},right:{width:s.right-e.right,height:s.height},bottom:{width:s.width,height:s.bottom-e.bottom},left:{width:e.left-s.left,height:s.height}},l=Object.keys(a).map(function(t){return it({key:t},a[t],{area:(e=a[t],e.width*e.height)});var e}).sort(function(t,e){return e.area-t.area}),c=l.filter(function(t){var e=t.width,i=t.height;return e>=n.clientWidth&&i>=n.clientHeight}),h=c.length>0?c[0].key:l[0].key,f=t.split("-")[1];return h+(f?"-"+f:"")}function ct(t,e,n){return st(n,G(e,n))}function ht(t){var e=getComputedStyle(t),n=parseFloat(e.marginTop)+parseFloat(e.marginBottom),i=parseFloat(e.marginLeft)+parseFloat(e.marginRight);return{width:t.offsetWidth+i,height:t.offsetHeight+n}}function ft(t){var e={left:"right",right:"left",bottom:"top",top:"bottom"};return t.replace(/left|right|bottom|top/g,function(t){return e[t]})}function ut(t,e,n){n=n.split("-")[0];var i=ht(t),r={width:i.width,height:i.height},o=-1!==["right","left"].indexOf(n),s=o?"top":"left",a=o?"left":"top",l=o?"height":"width",c=o?"width":"height";return r[s]=e[s]+e[l]/2-i[l]/2,r[a]=n===a?e[a]-i[c]:e[ft(a)],r}function dt(t,e){return Array.prototype.find?t.find(e):t.filter(e)[0]}function pt(t,e,n){return(void 0===n?t:t.slice(0,function(t,e,n){if(Array.prototype.findIndex)return t.findIndex(function(t){return t[e]===n});var i=dt(t,function(t){return t[e]===n});return t.indexOf(i)}(t,"name",n))).forEach(function(t){t.function&&console.warn("`modifier.function` is deprecated, use `modifier.fn`!");var n=t.function||t.fn;t.enabled&&B(n)&&(e.offsets.popper=rt(e.offsets.popper),e.offsets.reference=rt(e.offsets.reference),e=n(e,t))}),e}function gt(t,e){return t.some(function(t){var n=t.name;return t.enabled&&n===e})}function mt(t){for(var e=[!1,"ms","Webkit","Moz","O"],n=t.charAt(0).toUpperCase()+t.slice(1),i=0;i1&&void 0!==arguments[1]&&arguments[1],n=wt.indexOf(t),i=wt.slice(n+1).concat(wt.slice(0,n));return e?i.reverse():i}var At={FLIP:"flip",CLOCKWISE:"clockwise",COUNTERCLOCKWISE:"counterclockwise"};function Dt(t,e,n,i){var r=[0,0],o=-1!==["right","left"].indexOf(i),s=t.split(/(\+|\-)/).map(function(t){return t.trim()}),a=s.indexOf(dt(s,function(t){return-1!==t.search(/,|\s/)}));s[a]&&-1===s[a].indexOf(",")&&console.warn("Offsets separated by white space(s) are deprecated, use a comma (,) instead.");var l=/\s*,\s*|\s+/,c=-1!==a?[s.slice(0,a).concat([s[a].split(l)[0]]),[s[a].split(l)[1]].concat(s.slice(a+1))]:[s];return(c=c.map(function(t,i){var r=(1===i?!o:o)?"height":"width",s=!1;return t.reduce(function(t,e){return""===t[t.length-1]&&-1!==["+","-"].indexOf(e)?(t[t.length-1]=e,s=!0,t):s?(t[t.length-1]+=e,s=!1,t):t.concat(e)},[]).map(function(t){return function(t,e,n,i){var r=t.match(/((?:\-|\+)?\d*\.?\d*)(.*)/),o=+r[1],s=r[2];if(!o)return t;if(0===s.indexOf("%")){var a=void 0;switch(s){case"%p":a=n;break;case"%":case"%r":default:a=i}return rt(a)[e]/100*o}if("vh"===s||"vw"===s)return("vh"===s?Math.max(document.documentElement.clientHeight,window.innerHeight||0):Math.max(document.documentElement.clientWidth,window.innerWidth||0))/100*o;return o}(t,r,e,n)})})).forEach(function(t,e){t.forEach(function(n,i){yt(n)&&(r[e]+=n*("-"===t[i-1]?-1:1))})}),r}var St={placement:"bottom",eventsEnabled:!0,removeOnDestroy:!1,onCreate:function(){},onUpdate:function(){},modifiers:{shift:{order:100,enabled:!0,fn:function(t){var e=t.placement,n=e.split("-")[0],i=e.split("-")[1];if(i){var r=t.offsets,o=r.reference,s=r.popper,a=-1!==["bottom","top"].indexOf(n),l=a?"left":"top",c=a?"width":"height",h={start:nt({},l,o[l]),end:nt({},l,o[l]+o[c]-s[c])};t.offsets.popper=it({},s,h[i])}return t}},offset:{order:200,enabled:!0,fn:function(t,e){var n=e.offset,i=t.placement,r=t.offsets,o=r.popper,s=r.reference,a=i.split("-")[0],l=void 0;return l=yt(+n)?[+n,0]:Dt(n,o,s,a),"left"===a?(o.top+=l[0],o.left-=l[1]):"right"===a?(o.top+=l[0],o.left+=l[1]):"top"===a?(o.left+=l[0],o.top-=l[1]):"bottom"===a&&(o.left+=l[0],o.top+=l[1]),t.popper=o,t},offset:0},preventOverflow:{order:300,enabled:!0,fn:function(t,e){var n=e.boundariesElement||Q(t.instance.popper);t.instance.reference===n&&(n=Q(n));var i=at(t.instance.popper,t.instance.reference,e.padding,n);e.boundaries=i;var r=e.priority,o=t.offsets.popper,s={primary:function(t){var n=o[t];return o[t]i[t]&&!e.escapeWithReference&&(r=Math.min(o[n],i[t]-("right"===t?o.width:o.height))),nt({},n,r)}};return r.forEach(function(t){var e=-1!==["left","top"].indexOf(t)?"primary":"secondary";o=it({},o,s[e](t))}),t.offsets.popper=o,t},priority:["left","right","top","bottom"],padding:5,boundariesElement:"scrollParent"},keepTogether:{order:400,enabled:!0,fn:function(t){var e=t.offsets,n=e.popper,i=e.reference,r=t.placement.split("-")[0],o=Math.floor,s=-1!==["top","bottom"].indexOf(r),a=s?"right":"bottom",l=s?"left":"top",c=s?"width":"height";return n[a]o(i[a])&&(t.offsets.popper[l]=o(i[a])),t}},arrow:{order:500,enabled:!0,fn:function(t,e){var n;if(!Tt(t.instance.modifiers,"arrow","keepTogether"))return t;var i=e.element;if("string"==typeof i){if(!(i=t.instance.popper.querySelector(i)))return t}else if(!t.instance.popper.contains(i))return console.warn("WARNING: `arrow.element` must be child of its popper element!"),t;var r=t.placement.split("-")[0],o=t.offsets,s=o.popper,a=o.reference,l=-1!==["left","right"].indexOf(r),c=l?"height":"width",h=l?"Top":"Left",f=h.toLowerCase(),u=l?"left":"top",d=l?"bottom":"right",p=ht(i)[c];a[d]-ps[d]&&(t.offsets.popper[f]+=a[f]+p-s[d]),t.offsets.popper=rt(t.offsets.popper);var g=a[f]+a[c]/2-p/2,m=F(t.instance.popper),_=parseFloat(m["margin"+h],10),v=parseFloat(m["border"+h+"Width"],10),E=g-t.offsets.popper[f]-_-v;return E=Math.max(Math.min(s[c]-p,E),0),t.arrowElement=i,t.offsets.arrow=(nt(n={},f,Math.round(E)),nt(n,u,""),n),t},element:"[x-arrow]"},flip:{order:600,enabled:!0,fn:function(t,e){if(gt(t.instance.modifiers,"inner"))return t;if(t.flipped&&t.placement===t.originalPlacement)return t;var n=at(t.instance.popper,t.instance.reference,e.padding,e.boundariesElement),i=t.placement.split("-")[0],r=ft(i),o=t.placement.split("-")[1]||"",s=[];switch(e.behavior){case At.FLIP:s=[i,r];break;case At.CLOCKWISE:s=It(i);break;case At.COUNTERCLOCKWISE:s=It(i,!0);break;default:s=e.behavior}return s.forEach(function(a,l){if(i!==a||s.length===l+1)return t;i=t.placement.split("-")[0],r=ft(i);var c,h=t.offsets.popper,f=t.offsets.reference,u=Math.floor,d="left"===i&&u(h.right)>u(f.left)||"right"===i&&u(h.left)u(f.top)||"bottom"===i&&u(h.top)u(n.right),m=u(h.top)u(n.bottom),v="left"===i&&p||"right"===i&&g||"top"===i&&m||"bottom"===i&&_,E=-1!==["top","bottom"].indexOf(i),y=!!e.flipVariations&&(E&&"start"===o&&p||E&&"end"===o&&g||!E&&"start"===o&&m||!E&&"end"===o&&_);(d||v||y)&&(t.flipped=!0,(d||v)&&(i=s[l+1]),y&&(o="end"===(c=o)?"start":"start"===c?"end":c),t.placement=i+(o?"-"+o:""),t.offsets.popper=it({},t.offsets.popper,ut(t.instance.popper,t.offsets.reference,t.placement)),t=pt(t.instance.modifiers,t,"flip"))}),t},behavior:"flip",padding:5,boundariesElement:"viewport"},inner:{order:700,enabled:!1,fn:function(t){var e=t.placement,n=e.split("-")[0],i=t.offsets,r=i.popper,o=i.reference,s=-1!==["left","right"].indexOf(n),a=-1===["top","left"].indexOf(n);return r[s?"left":"top"]=o[n]-(a?r[s?"width":"height"]:0),t.placement=ft(e),t.offsets.popper=rt(r),t}},hide:{order:800,enabled:!0,fn:function(t){if(!Tt(t.instance.modifiers,"hide","preventOverflow"))return t;var e=t.offsets.reference,n=dt(t.instance.modifiers,function(t){return"preventOverflow"===t.name}).boundaries;if(e.bottomn.right||e.top>n.bottom||e.right2&&void 0!==arguments[2]?arguments[2]:{};tt(this,t),this.scheduleUpdate=function(){return requestAnimationFrame(i.update)},this.update=U(this.update.bind(this)),this.options=it({},t.Defaults,r),this.state={isDestroyed:!1,isCreated:!1,scrollParents:[]},this.reference=e&&e.jquery?e[0]:e,this.popper=n&&n.jquery?n[0]:n,this.options.modifiers={},Object.keys(it({},t.Defaults.modifiers,r.modifiers)).forEach(function(e){i.options.modifiers[e]=it({},t.Defaults.modifiers[e]||{},r.modifiers?r.modifiers[e]:{})}),this.modifiers=Object.keys(this.options.modifiers).map(function(t){return it({name:t},i.options.modifiers[t])}).sort(function(t,e){return t.order-e.order}),this.modifiers.forEach(function(t){t.enabled&&B(t.onLoad)&&t.onLoad(i.reference,i.popper,i.options,t,i.state)}),this.update();var o=this.options.eventsEnabled;o&&this.enableEventListeners(),this.state.eventsEnabled=o}return et(t,[{key:"update",value:function(){return function(){if(!this.state.isDestroyed){var t={instance:this,styles:{},arrowStyles:{},attributes:{},flipped:!1,offsets:{}};t.offsets.reference=ct(this.state,this.popper,this.reference),t.placement=lt(this.options.placement,t.offsets.reference,this.popper,this.reference,this.options.modifiers.flip.boundariesElement,this.options.modifiers.flip.padding),t.originalPlacement=t.placement,t.offsets.popper=ut(this.popper,t.offsets.reference,t.placement),t.offsets.popper.position="absolute",t=pt(this.modifiers,t),this.state.isCreated?this.options.onUpdate(t):(this.state.isCreated=!0,this.options.onCreate(t))}}.call(this)}},{key:"destroy",value:function(){return function(){return this.state.isDestroyed=!0,gt(this.modifiers,"applyStyle")&&(this.popper.removeAttribute("x-placement"),this.popper.style.left="",this.popper.style.position="",this.popper.style.top="",this.popper.style[mt("transform")]=""),this.disableEventListeners(),this.options.removeOnDestroy&&this.popper.parentNode.removeChild(this.popper),this}.call(this)}},{key:"enableEventListeners",value:function(){return function(){this.state.eventsEnabled||(this.state=vt(this.reference,this.options,this.state,this.scheduleUpdate))}.call(this)}},{key:"disableEventListeners",value:function(){return Et.call(this)}}]),t}();Ot.Utils=("undefined"!=typeof window?window:global).PopperUtils,Ot.placements=Ct,Ot.Defaults=St;var Nt=function(t){var e="dropdown",n="bs.dropdown",o="."+n,s=t.fn[e],a=new RegExp("38|40|27"),l={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,CLICK:"click"+o,CLICK_DATA_API:"click"+o+".data-api",KEYDOWN_DATA_API:"keydown"+o+".data-api",KEYUP_DATA_API:"keyup"+o+".data-api"},c="disabled",h="show",f="dropup",u="dropright",d="dropleft",p="dropdown-menu-right",g="dropdown-menu-left",m="position-static",_='[data-toggle="dropdown"]',v=".dropdown form",E=".dropdown-menu",y=".navbar-nav",b=".dropdown-menu .dropdown-item:not(.disabled)",T="top-start",C="top-end",w="bottom-start",I="bottom-end",A="right-start",D="left-start",S={offset:0,flip:!0,boundary:"scrollParent"},O={offset:"(number|string|function)",flip:"boolean",boundary:"(string|element)"},N=function(){function s(t,e){this._element=t,this._popper=null,this._config=this._getConfig(e),this._menu=this._getMenuElement(),this._inNavbar=this._detectNavbar(),this._addEventListeners()}var v=s.prototype;return v.toggle=function(){if(!this._element.disabled&&!t(this._element).hasClass(c)){var e=s._getParentFromElement(this._element),n=t(this._menu).hasClass(h);if(s._clearMenus(),!n){var i={relatedTarget:this._element},r=t.Event(l.SHOW,i);if(t(e).trigger(r),!r.isDefaultPrevented()){if(!this._inNavbar){if("undefined"==typeof Ot)throw new TypeError("Bootstrap dropdown require Popper.js (https://popper.js.org)");var o=this._element;t(e).hasClass(f)&&(t(this._menu).hasClass(g)||t(this._menu).hasClass(p))&&(o=e),"scrollParent"!==this._config.boundary&&t(e).addClass(m),this._popper=new Ot(o,this._menu,this._getPopperConfig())}"ontouchstart"in document.documentElement&&0===t(e).closest(y).length&&t("body").children().on("mouseover",null,t.noop),this._element.focus(),this._element.setAttribute("aria-expanded",!0),t(this._menu).toggleClass(h),t(e).toggleClass(h).trigger(t.Event(l.SHOWN,i))}}}},v.dispose=function(){t.removeData(this._element,n),t(this._element).off(o),this._element=null,this._menu=null,null!==this._popper&&(this._popper.destroy(),this._popper=null)},v.update=function(){this._inNavbar=this._detectNavbar(),null!==this._popper&&this._popper.scheduleUpdate()},v._addEventListeners=function(){var e=this;t(this._element).on(l.CLICK,function(t){t.preventDefault(),t.stopPropagation(),e.toggle()})},v._getConfig=function(n){return n=r({},this.constructor.Default,t(this._element).data(),n),k.typeCheckConfig(e,n,this.constructor.DefaultType),n},v._getMenuElement=function(){if(!this._menu){var e=s._getParentFromElement(this._element);this._menu=t(e).find(E)[0]}return this._menu},v._getPlacement=function(){var e=t(this._element).parent(),n=w;return e.hasClass(f)?(n=T,t(this._menu).hasClass(p)&&(n=C)):e.hasClass(u)?n=A:e.hasClass(d)?n=D:t(this._menu).hasClass(p)&&(n=I),n},v._detectNavbar=function(){return t(this._element).closest(".navbar").length>0},v._getPopperConfig=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t._config.offset(e.offsets)||{}),e}:e.offset=this._config.offset,{placement:this._getPlacement(),modifiers:{offset:e,flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}}},s._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n);if(i||(i=new s(this,"object"==typeof e?e:null),t(this).data(n,i)),"string"==typeof e){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}})},s._clearMenus=function(e){if(!e||3!==e.which&&("keyup"!==e.type||9===e.which))for(var i=t.makeArray(t(_)),r=0;r0&&o--,40===e.which&&odocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},g._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},g._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},f="show",u="out",d={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,INSERTED:"inserted"+o,CLICK:"click"+o,FOCUSIN:"focusin"+o,FOCUSOUT:"focusout"+o,MOUSEENTER:"mouseenter"+o,MOUSELEAVE:"mouseleave"+o},p="fade",g="show",m=".tooltip-inner",_=".arrow",v="hover",E="focus",y="click",b="manual",T=function(){function s(t,e){if("undefined"==typeof Ot)throw new TypeError("Bootstrap tooltips require Popper.js (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var T=s.prototype;return T.enable=function(){this._isEnabled=!0},T.disable=function(){this._isEnabled=!1},T.toggleEnabled=function(){this._isEnabled=!this._isEnabled},T.toggle=function(e){if(this._isEnabled)if(e){var n=this.constructor.DATA_KEY,i=t(e.currentTarget).data(n);i||(i=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(n,i)),i._activeTrigger.click=!i._activeTrigger.click,i._isWithActiveTrigger()?i._enter(null,i):i._leave(null,i)}else{if(t(this.getTipElement()).hasClass(g))return void this._leave(null,this);this._enter(null,this)}},T.dispose=function(){clearTimeout(this._timeout),t.removeData(this.element,this.constructor.DATA_KEY),t(this.element).off(this.constructor.EVENT_KEY),t(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&t(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,null!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},T.show=function(){var e=this;if("none"===t(this.element).css("display"))throw new Error("Please use show on visible elements");var n=t.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){t(this.element).trigger(n);var i=t.contains(this.element.ownerDocument.documentElement,this.element);if(n.isDefaultPrevented()||!i)return;var r=this.getTipElement(),o=k.getUID(this.constructor.NAME);r.setAttribute("id",o),this.element.setAttribute("aria-describedby",o),this.setContent(),this.config.animation&&t(r).addClass(p);var a="function"==typeof this.config.placement?this.config.placement.call(this,r,this.element):this.config.placement,l=this._getAttachment(a);this.addAttachmentClass(l);var c=!1===this.config.container?document.body:t(this.config.container);t(r).data(this.constructor.DATA_KEY,this),t.contains(this.element.ownerDocument.documentElement,this.tip)||t(r).appendTo(c),t(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new Ot(this.element,r,{placement:l,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:_},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){e._handlePopperPlacementChange(t)}}),t(r).addClass(g),"ontouchstart"in document.documentElement&&t("body").children().on("mouseover",null,t.noop);var h=function(){e.config.animation&&e._fixTransition();var n=e._hoverState;e._hoverState=null,t(e.element).trigger(e.constructor.Event.SHOWN),n===u&&e._leave(null,e)};k.supportsTransitionEnd()&&t(this.tip).hasClass(p)?t(this.tip).one(k.TRANSITION_END,h).emulateTransitionEnd(s._TRANSITION_DURATION):h()}},T.hide=function(e){var n=this,i=this.getTipElement(),r=t.Event(this.constructor.Event.HIDE),o=function(){n._hoverState!==f&&i.parentNode&&i.parentNode.removeChild(i),n._cleanTipClass(),n.element.removeAttribute("aria-describedby"),t(n.element).trigger(n.constructor.Event.HIDDEN),null!==n._popper&&n._popper.destroy(),e&&e()};t(this.element).trigger(r),r.isDefaultPrevented()||(t(i).removeClass(g),"ontouchstart"in document.documentElement&&t("body").children().off("mouseover",null,t.noop),this._activeTrigger[y]=!1,this._activeTrigger[E]=!1,this._activeTrigger[v]=!1,k.supportsTransitionEnd()&&t(this.tip).hasClass(p)?t(i).one(k.TRANSITION_END,o).emulateTransitionEnd(150):o(),this._hoverState="")},T.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},T.isWithContent=function(){return Boolean(this.getTitle())},T.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-tooltip-"+e)},T.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},T.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(m),this.getTitle()),e.removeClass(p+" "+g)},T.setElementContent=function(e,n){var i=this.config.html;"object"==typeof n&&(n.nodeType||n.jquery)?i?t(n).parent().is(e)||e.empty().append(n):e.text(t(n).text()):e[i?"html":"text"](n)},T.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},T._getAttachment=function(t){return c[t.toUpperCase()]},T._setListeners=function(){var e=this;this.config.trigger.split(" ").forEach(function(n){if("click"===n)t(e.element).on(e.constructor.Event.CLICK,e.config.selector,function(t){return e.toggle(t)});else if(n!==b){var i=n===v?e.constructor.Event.MOUSEENTER:e.constructor.Event.FOCUSIN,r=n===v?e.constructor.Event.MOUSELEAVE:e.constructor.Event.FOCUSOUT;t(e.element).on(i,e.config.selector,function(t){return e._enter(t)}).on(r,e.config.selector,function(t){return e._leave(t)})}t(e.element).closest(".modal").on("hide.bs.modal",function(){return e.hide()})}),this.config.selector?this.config=r({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},T._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},T._enter=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusin"===e.type?E:v]=!0),t(n.getTipElement()).hasClass(g)||n._hoverState===f?n._hoverState=f:(clearTimeout(n._timeout),n._hoverState=f,n.config.delay&&n.config.delay.show?n._timeout=setTimeout(function(){n._hoverState===f&&n.show()},n.config.delay.show):n.show())},T._leave=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusout"===e.type?E:v]=!1),n._isWithActiveTrigger()||(clearTimeout(n._timeout),n._hoverState=u,n.config.delay&&n.config.delay.hide?n._timeout=setTimeout(function(){n._hoverState===u&&n.hide()},n.config.delay.hide):n.hide())},T._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},T._getConfig=function(n){return"number"==typeof(n=r({},this.constructor.Default,t(this.element).data(),n)).delay&&(n.delay={show:n.delay,hide:n.delay}),"number"==typeof n.title&&(n.title=n.title.toString()),"number"==typeof n.content&&(n.content=n.content.toString()),k.typeCheckConfig(e,n,this.constructor.DefaultType),n},T._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},T._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(a);null!==n&&n.length>0&&e.removeClass(n.join(""))},T._handlePopperPlacementChange=function(t){this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},T._fixTransition=function(){var e=this.getTipElement(),n=this.config.animation;null===e.getAttribute("x-placement")&&(t(e).removeClass(p),this.config.animation=!1,this.hide(),this.show(),this.config.animation=n)},s._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),r="object"==typeof e&&e;if((i||!/dispose|hide/.test(e))&&(i||(i=new s(this,r),t(this).data(n,i)),"string"==typeof e)){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}})},i(s,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return h}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return n}},{key:"Event",get:function(){return d}},{key:"EVENT_KEY",get:function(){return o}},{key:"DefaultType",get:function(){return l}}]),s}();return t.fn[e]=T._jQueryInterface,t.fn[e].Constructor=T,t.fn[e].noConflict=function(){return t.fn[e]=s,T._jQueryInterface},T}(e),Pt=function(t){var e="popover",n="bs.popover",o="."+n,s=t.fn[e],a=new RegExp("(^|\\s)bs-popover\\S+","g"),l=r({},Lt.Default,{placement:"right",trigger:"click",content:"",template:''}),c=r({},Lt.DefaultType,{content:"(string|element|function)"}),h="fade",f="show",u=".popover-header",d=".popover-body",p={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,INSERTED:"inserted"+o,CLICK:"click"+o,FOCUSIN:"focusin"+o,FOCUSOUT:"focusout"+o,MOUSEENTER:"mouseenter"+o,MOUSELEAVE:"mouseleave"+o},g=function(r){var s,g;function m(){return r.apply(this,arguments)||this}g=r,(s=m).prototype=Object.create(g.prototype),s.prototype.constructor=s,s.__proto__=g;var _=m.prototype;return _.isWithContent=function(){return this.getTitle()||this._getContent()},_.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-popover-"+e)},_.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},_.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(u),this.getTitle());var n=this._getContent();"function"==typeof n&&(n=n.call(this.element)),this.setElementContent(e.find(d),n),e.removeClass(h+" "+f)},_._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},_._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(a);null!==n&&n.length>0&&e.removeClass(n.join(""))},m._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),r="object"==typeof e?e:null;if((i||!/destroy|hide/.test(e))&&(i||(i=new m(this,r),t(this).data(n,i)),"string"==typeof e)){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}})},i(m,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return l}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return n}},{key:"Event",get:function(){return p}},{key:"EVENT_KEY",get:function(){return o}},{key:"DefaultType",get:function(){return c}}]),m}(Lt);return t.fn[e]=g._jQueryInterface,t.fn[e].Constructor=g,t.fn[e].noConflict=function(){return t.fn[e]=s,g._jQueryInterface},g}(e),xt=function(t){var e="scrollspy",n="bs.scrollspy",o="."+n,s=t.fn[e],a={offset:10,method:"auto",target:""},l={offset:"number",method:"string",target:"(string|element)"},c={ACTIVATE:"activate"+o,SCROLL:"scroll"+o,LOAD_DATA_API:"load"+o+".data-api"},h="dropdown-item",f="active",u={DATA_SPY:'[data-spy="scroll"]',ACTIVE:".active",NAV_LIST_GROUP:".nav, .list-group",NAV_LINKS:".nav-link",NAV_ITEMS:".nav-item",LIST_ITEMS:".list-group-item",DROPDOWN:".dropdown",DROPDOWN_ITEMS:".dropdown-item",DROPDOWN_TOGGLE:".dropdown-toggle"},d="offset",p="position",g=function(){function s(e,n){var i=this;this._element=e,this._scrollElement="BODY"===e.tagName?window:e,this._config=this._getConfig(n),this._selector=this._config.target+" "+u.NAV_LINKS+","+this._config.target+" "+u.LIST_ITEMS+","+this._config.target+" "+u.DROPDOWN_ITEMS,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,t(this._scrollElement).on(c.SCROLL,function(t){return i._process(t)}),this.refresh(),this._process()}var g=s.prototype;return g.refresh=function(){var e=this,n=this._scrollElement===this._scrollElement.window?d:p,i="auto"===this._config.method?n:this._config.method,r=i===p?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),t.makeArray(t(this._selector)).map(function(e){var n,o=k.getSelectorFromElement(e);if(o&&(n=t(o)[0]),n){var s=n.getBoundingClientRect();if(s.width||s.height)return[t(n)[i]().top+r,o]}return null}).filter(function(t){return t}).sort(function(t,e){return t[0]-e[0]}).forEach(function(t){e._offsets.push(t[0]),e._targets.push(t[1])})},g.dispose=function(){t.removeData(this._element,n),t(this._scrollElement).off(o),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},g._getConfig=function(n){if("string"!=typeof(n=r({},a,n)).target){var i=t(n.target).attr("id");i||(i=k.getUID(e),t(n.target).attr("id",i)),n.target="#"+i}return k.typeCheckConfig(e,n,l),n},g._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},g._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},g._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},g._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var r=this._offsets.length;r--;){this._activeTarget!==this._targets[r]&&t>=this._offsets[r]&&("undefined"==typeof this._offsets[r+1]||t=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}(e),t.Util=k,t.Alert=L,t.Button=P,t.Carousel=x,t.Collapse=R,t.Dropdown=Nt,t.Modal=kt,t.Popover=Pt,t.Scrollspy=xt,t.Tab=Rt,t.Tooltip=Lt,Object.defineProperty(t,"__esModule",{value:!0})}); //# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/examples/libs/bootstrap.js b/examples/libs/bootstrap.js index 241d11b..8a2e99a 100644 --- a/examples/libs/bootstrap.js +++ b/examples/libs/bootstrap.js @@ -1,4494 +1,2377 @@ /*! - * Bootstrap v5.3.3 (https://getbootstrap.com/) - * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@popperjs/core')) : - typeof define === 'function' && define.amd ? define(['@popperjs/core'], factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.bootstrap = factory(global.Popper)); -})(this, (function (Popper) { 'use strict'; - - function _interopNamespaceDefault(e) { - const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } }); - if (e) { - for (const k in e) { - if (k !== 'default') { - const d = Object.getOwnPropertyDescriptor(e, k); - Object.defineProperty(n, k, d.get ? d : { - enumerable: true, - get: () => e[k] - }); - } - } - } - n.default = e; - return Object.freeze(n); + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under the MIT license + */ + +if (typeof jQuery === 'undefined') { + throw new Error('Bootstrap\'s JavaScript requires jQuery') +} + ++function ($) { + 'use strict'; + var version = $.fn.jquery.split(' ')[0].split('.') + if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) { + throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4') } +}(jQuery); - const Popper__namespace = /*#__PURE__*/_interopNamespaceDefault(Popper); +/* ======================================================================== + * Bootstrap: transition.js v3.3.7 + * http://getbootstrap.com/javascript/#transitions + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ - /** - * -------------------------------------------------------------------------- - * Bootstrap dom/data.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - /** - * Constants - */ ++function ($) { + 'use strict'; - const elementMap = new Map(); - const Data = { - set(element, key, instance) { - if (!elementMap.has(element)) { - elementMap.set(element, new Map()); - } - const instanceMap = elementMap.get(element); - - // make it clear we only want one instance per element - // can be removed later when multiple key/instances are fine to be used - if (!instanceMap.has(key) && instanceMap.size !== 0) { - // eslint-disable-next-line no-console - console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`); - return; - } - instanceMap.set(key, instance); - }, - get(element, key) { - if (elementMap.has(element)) { - return elementMap.get(element).get(key) || null; - } - return null; - }, - remove(element, key) { - if (!elementMap.has(element)) { - return; - } - const instanceMap = elementMap.get(element); - instanceMap.delete(key); + // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) + // ============================================================ - // free up element references if there are no instances left for an element - if (instanceMap.size === 0) { - elementMap.delete(element); - } - } - }; - - /** - * -------------------------------------------------------------------------- - * Bootstrap util/index.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - - const MAX_UID = 1000000; - const MILLISECONDS_MULTIPLIER = 1000; - const TRANSITION_END = 'transitionend'; - - /** - * Properly escape IDs selectors to handle weird IDs - * @param {string} selector - * @returns {string} - */ - const parseSelector = selector => { - if (selector && window.CSS && window.CSS.escape) { - // document.querySelector needs escaping to handle IDs (html5+) containing for instance / - selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`); - } - return selector; - }; + function transitionEnd() { + var el = document.createElement('bootstrap') - // Shout-out Angus Croll (https://goo.gl/pxwQGp) - const toType = object => { - if (object === null || object === undefined) { - return `${object}`; - } - return Object.prototype.toString.call(object).match(/\s([a-z]+)/i)[1].toLowerCase(); - }; - - /** - * Public Util API - */ - - const getUID = prefix => { - do { - prefix += Math.floor(Math.random() * MAX_UID); - } while (document.getElementById(prefix)); - return prefix; - }; - const getTransitionDurationFromElement = element => { - if (!element) { - return 0; + var transEndEventNames = { + WebkitTransition : 'webkitTransitionEnd', + MozTransition : 'transitionend', + OTransition : 'oTransitionEnd otransitionend', + transition : 'transitionend' } - // Get transition-duration of the element - let { - transitionDuration, - transitionDelay - } = window.getComputedStyle(element); - const floatTransitionDuration = Number.parseFloat(transitionDuration); - const floatTransitionDelay = Number.parseFloat(transitionDelay); - - // Return 0 if element or transition duration is not found - if (!floatTransitionDuration && !floatTransitionDelay) { - return 0; + for (var name in transEndEventNames) { + if (el.style[name] !== undefined) { + return { end: transEndEventNames[name] } + } } - // If multiple durations are defined, take the first - transitionDuration = transitionDuration.split(',')[0]; - transitionDelay = transitionDelay.split(',')[0]; - return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER; - }; - const triggerTransitionEnd = element => { - element.dispatchEvent(new Event(TRANSITION_END)); - }; - const isElement = object => { - if (!object || typeof object !== 'object') { - return false; - } - if (typeof object.jquery !== 'undefined') { - object = object[0]; - } - return typeof object.nodeType !== 'undefined'; - }; - const getElement = object => { - // it's a jQuery object or a node element - if (isElement(object)) { - return object.jquery ? object[0] : object; - } - if (typeof object === 'string' && object.length > 0) { - return document.querySelector(parseSelector(object)); - } - return null; - }; - const isVisible = element => { - if (!isElement(element) || element.getClientRects().length === 0) { - return false; - } - const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'; - // Handle `details` element as its content may falsie appear visible when it is closed - const closedDetails = element.closest('details:not([open])'); - if (!closedDetails) { - return elementIsVisible; - } - if (closedDetails !== element) { - const summary = element.closest('summary'); - if (summary && summary.parentNode !== closedDetails) { - return false; - } - if (summary === null) { - return false; + return false // explicit for ie8 ( ._.) + } + + // http://blog.alexmaccaw.com/css-transitions + $.fn.emulateTransitionEnd = function (duration) { + var called = false + var $el = this + $(this).one('bsTransitionEnd', function () { called = true }) + var callback = function () { if (!called) $($el).trigger($.support.transition.end) } + setTimeout(callback, duration) + return this + } + + $(function () { + $.support.transition = transitionEnd() + + if (!$.support.transition) return + + $.event.special.bsTransitionEnd = { + bindType: $.support.transition.end, + delegateType: $.support.transition.end, + handle: function (e) { + if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) } } - return elementIsVisible; - }; - const isDisabled = element => { - if (!element || element.nodeType !== Node.ELEMENT_NODE) { - return true; - } - if (element.classList.contains('disabled')) { - return true; - } - if (typeof element.disabled !== 'undefined') { - return element.disabled; - } - return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'; - }; - const findShadowRoot = element => { - if (!document.documentElement.attachShadow) { - return null; - } + }) - // Can find the shadow root otherwise it'll return the document - if (typeof element.getRootNode === 'function') { - const root = element.getRootNode(); - return root instanceof ShadowRoot ? root : null; - } - if (element instanceof ShadowRoot) { - return element; +}(jQuery); + +/* ======================================================================== + * Bootstrap: alert.js v3.3.7 + * http://getbootstrap.com/javascript/#alerts + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // ALERT CLASS DEFINITION + // ====================== + + var dismiss = '[data-dismiss="alert"]' + var Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.VERSION = '3.3.7' + + Alert.TRANSITION_DURATION = 150 + + Alert.prototype.close = function (e) { + var $this = $(this) + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 } - // when we don't find a shadow root - if (!element.parentNode) { - return null; + var $parent = $(selector === '#' ? [] : selector) + + if (e) e.preventDefault() + + if (!$parent.length) { + $parent = $this.closest('.alert') } - return findShadowRoot(element.parentNode); - }; - const noop = () => {}; - - /** - * Trick to restart an element's animation - * - * @param {HTMLElement} element - * @return void - * - * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation - */ - const reflow = element => { - element.offsetHeight; // eslint-disable-line no-unused-expressions - }; - const getjQuery = () => { - if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) { - return window.jQuery; + + $parent.trigger(e = $.Event('close.bs.alert')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + // detach from parent, fire event then clean up data + $parent.detach().trigger('closed.bs.alert').remove() } - return null; - }; - const DOMContentLoadedCallbacks = []; - const onDOMContentLoaded = callback => { - if (document.readyState === 'loading') { - // add listener on the first call when the document is in loading state - if (!DOMContentLoadedCallbacks.length) { - document.addEventListener('DOMContentLoaded', () => { - for (const callback of DOMContentLoadedCallbacks) { - callback(); - } - }); + + $.support.transition && $parent.hasClass('fade') ? + $parent + .one('bsTransitionEnd', removeElement) + .emulateTransitionEnd(Alert.TRANSITION_DURATION) : + removeElement() + } + + + // ALERT PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.alert') + + if (!data) $this.data('bs.alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.alert + + $.fn.alert = Plugin + $.fn.alert.Constructor = Alert + + + // ALERT NO CONFLICT + // ================= + + $.fn.alert.noConflict = function () { + $.fn.alert = old + return this + } + + + // ALERT DATA-API + // ============== + + $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: button.js v3.3.7 + * http://getbootstrap.com/javascript/#buttons + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // BUTTON PUBLIC CLASS DEFINITION + // ============================== + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Button.DEFAULTS, options) + this.isLoading = false + } + + Button.VERSION = '3.3.7' + + Button.DEFAULTS = { + loadingText: 'loading...' + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + var $el = this.$element + var val = $el.is('input') ? 'val' : 'html' + var data = $el.data() + + state += 'Text' + + if (data.resetText == null) $el.data('resetText', $el[val]()) + + // push to event loop to allow forms to submit + setTimeout($.proxy(function () { + $el[val](data[state] == null ? this.options[state] : data[state]) + + if (state == 'loadingText') { + this.isLoading = true + $el.addClass(d).attr(d, d).prop(d, true) + } else if (this.isLoading) { + this.isLoading = false + $el.removeClass(d).removeAttr(d).prop(d, false) } - DOMContentLoadedCallbacks.push(callback); + }, this), 0) + } + + Button.prototype.toggle = function () { + var changed = true + var $parent = this.$element.closest('[data-toggle="buttons"]') + + if ($parent.length) { + var $input = this.$element.find('input') + if ($input.prop('type') == 'radio') { + if ($input.prop('checked')) changed = false + $parent.find('.active').removeClass('active') + this.$element.addClass('active') + } else if ($input.prop('type') == 'checkbox') { + if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false + this.$element.toggleClass('active') + } + $input.prop('checked', this.$element.hasClass('active')) + if (changed) $input.trigger('change') } else { - callback(); + this.$element.attr('aria-pressed', !this.$element.hasClass('active')) + this.$element.toggleClass('active') } - }; - const isRTL = () => document.documentElement.dir === 'rtl'; - const defineJQueryPlugin = plugin => { - onDOMContentLoaded(() => { - const $ = getjQuery(); - /* istanbul ignore if */ - if ($) { - const name = plugin.NAME; - const JQUERY_NO_CONFLICT = $.fn[name]; - $.fn[name] = plugin.jQueryInterface; - $.fn[name].Constructor = plugin; - $.fn[name].noConflict = () => { - $.fn[name] = JQUERY_NO_CONFLICT; - return plugin.jQueryInterface; - }; - } - }); - }; - const execute = (possibleCallback, args = [], defaultValue = possibleCallback) => { - return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue; - }; - const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => { - if (!waitForTransition) { - execute(callback); - return; - } - const durationPadding = 5; - const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding; - let called = false; - const handler = ({ - target - }) => { - if (target !== transitionElement) { - return; - } - called = true; - transitionElement.removeEventListener(TRANSITION_END, handler); - execute(callback); - }; - transitionElement.addEventListener(TRANSITION_END, handler); - setTimeout(() => { - if (!called) { - triggerTransitionEnd(transitionElement); - } - }, emulatedDuration); - }; - - /** - * Return the previous/next element of a list. - * - * @param {array} list The list of elements - * @param activeElement The active element - * @param shouldGetNext Choose to get next or previous element - * @param isCycleAllowed - * @return {Element|elem} The proper element - */ - const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => { - const listLength = list.length; - let index = list.indexOf(activeElement); - - // if the element does not exist in the list return an element - // depending on the direction and if cycle is allowed - if (index === -1) { - return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]; - } - index += shouldGetNext ? 1 : -1; - if (isCycleAllowed) { - index = (index + listLength) % listLength; - } - return list[Math.max(0, Math.min(index, listLength - 1))]; - }; - - /** - * -------------------------------------------------------------------------- - * Bootstrap dom/event-handler.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - - - /** - * Constants - */ - - const namespaceRegex = /[^.]*(?=\..*)\.|.*/; - const stripNameRegex = /\..*/; - const stripUidRegex = /::\d+$/; - const eventRegistry = {}; // Events storage - let uidEvent = 1; - const customEvents = { - mouseenter: 'mouseover', - mouseleave: 'mouseout' - }; - const nativeEvents = new Set(['click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', 'mousewheel', 'DOMMouseScroll', 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', 'keydown', 'keypress', 'keyup', 'orientationchange', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointerleave', 'pointercancel', 'gesturestart', 'gesturechange', 'gestureend', 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', 'error', 'abort', 'scroll']); - - /** - * Private methods - */ - - function makeEventUid(element, uid) { - return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++; } - function getElementEvents(element) { - const uid = makeEventUid(element); - element.uidEvent = uid; - eventRegistry[uid] = eventRegistry[uid] || {}; - return eventRegistry[uid]; + + + // BUTTON PLUGIN DEFINITION + // ======================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.button') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.button', (data = new Button(this, options))) + + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) } - function bootstrapHandler(element, fn) { - return function handler(event) { - hydrateObj(event, { - delegateTarget: element - }); - if (handler.oneOff) { - EventHandler.off(element, event.type, fn); - } - return fn.apply(element, [event]); - }; + + var old = $.fn.button + + $.fn.button = Plugin + $.fn.button.Constructor = Button + + + // BUTTON NO CONFLICT + // ================== + + $.fn.button.noConflict = function () { + $.fn.button = old + return this } - function bootstrapDelegationHandler(element, selector, fn) { - return function handler(event) { - const domElements = element.querySelectorAll(selector); - for (let { - target - } = event; target && target !== this; target = target.parentNode) { - for (const domElement of domElements) { - if (domElement !== target) { - continue; - } - hydrateObj(event, { - delegateTarget: target - }); - if (handler.oneOff) { - EventHandler.off(element, event.type, selector, fn); - } - return fn.apply(target, [event]); - } - } - }; + + + // BUTTON DATA-API + // =============== + + $(document) + .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { + var $btn = $(e.target).closest('.btn') + Plugin.call($btn, 'toggle') + if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) { + // Prevent double click on radios, and the double selections (so cancellation) on checkboxes + e.preventDefault() + // The target component still receive the focus + if ($btn.is('input,button')) $btn.trigger('focus') + else $btn.find('input:visible,button:visible').first().trigger('focus') + } + }) + .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { + $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: carousel.js v3.3.7 + * http://getbootstrap.com/javascript/#carousel + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CAROUSEL CLASS DEFINITION + // ========================= + + var Carousel = function (element, options) { + this.$element = $(element) + this.$indicators = this.$element.find('.carousel-indicators') + this.options = options + this.paused = null + this.sliding = null + this.interval = null + this.$active = null + this.$items = null + + this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) + + this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element + .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) + .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) } - function findHandler(events, callable, delegationSelector = null) { - return Object.values(events).find(event => event.callable === callable && event.delegationSelector === delegationSelector); + + Carousel.VERSION = '3.3.7' + + Carousel.TRANSITION_DURATION = 600 + + Carousel.DEFAULTS = { + interval: 5000, + pause: 'hover', + wrap: true, + keyboard: true } - function normalizeParameters(originalTypeEvent, handler, delegationFunction) { - const isDelegated = typeof handler === 'string'; - // TODO: tooltip passes `false` instead of selector, so we need to check - const callable = isDelegated ? delegationFunction : handler || delegationFunction; - let typeEvent = getTypeEvent(originalTypeEvent); - if (!nativeEvents.has(typeEvent)) { - typeEvent = originalTypeEvent; + + Carousel.prototype.keydown = function (e) { + if (/input|textarea/i.test(e.target.tagName)) return + switch (e.which) { + case 37: this.prev(); break + case 39: this.next(); break + default: return } - return [isDelegated, callable, typeEvent]; + + e.preventDefault() } - function addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) { - if (typeof originalTypeEvent !== 'string' || !element) { - return; - } - let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction); - - // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position - // this prevents the handler from being dispatched the same way as mouseover or mouseout does - if (originalTypeEvent in customEvents) { - const wrapFunction = fn => { - return function (event) { - if (!event.relatedTarget || event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget)) { - return fn.call(this, event); - } - }; - }; - callable = wrapFunction(callable); - } - const events = getElementEvents(element); - const handlers = events[typeEvent] || (events[typeEvent] = {}); - const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null); - if (previousFunction) { - previousFunction.oneOff = previousFunction.oneOff && oneOff; - return; - } - const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, '')); - const fn = isDelegated ? bootstrapDelegationHandler(element, handler, callable) : bootstrapHandler(element, callable); - fn.delegationSelector = isDelegated ? handler : null; - fn.callable = callable; - fn.oneOff = oneOff; - fn.uidEvent = uid; - handlers[uid] = fn; - element.addEventListener(typeEvent, fn, isDelegated); + + Carousel.prototype.cycle = function (e) { + e || (this.paused = false) + + this.interval && clearInterval(this.interval) + + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + + return this } - function removeHandler(element, events, typeEvent, handler, delegationSelector) { - const fn = findHandler(events[typeEvent], handler, delegationSelector); - if (!fn) { - return; - } - element.removeEventListener(typeEvent, fn, Boolean(delegationSelector)); - delete events[typeEvent][fn.uidEvent]; + + Carousel.prototype.getItemIndex = function (item) { + this.$items = item.parent().children('.item') + return this.$items.index(item || this.$active) } - function removeNamespacedHandlers(element, events, typeEvent, namespace) { - const storeElementEvent = events[typeEvent] || {}; - for (const [handlerKey, event] of Object.entries(storeElementEvent)) { - if (handlerKey.includes(namespace)) { - removeHandler(element, events, typeEvent, event.callable, event.delegationSelector); - } - } + + Carousel.prototype.getItemForDirection = function (direction, active) { + var activeIndex = this.getItemIndex(active) + var willWrap = (direction == 'prev' && activeIndex === 0) + || (direction == 'next' && activeIndex == (this.$items.length - 1)) + if (willWrap && !this.options.wrap) return active + var delta = direction == 'prev' ? -1 : 1 + var itemIndex = (activeIndex + delta) % this.$items.length + return this.$items.eq(itemIndex) } - function getTypeEvent(event) { - // allow to get the native events from namespaced events ('click.bs.button' --> 'click') - event = event.replace(stripNameRegex, ''); - return customEvents[event] || event; + + Carousel.prototype.to = function (pos) { + var that = this + var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) + + if (pos > (this.$items.length - 1) || pos < 0) return + + if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" + if (activeIndex == pos) return this.pause().cycle() + + return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) } - const EventHandler = { - on(element, event, handler, delegationFunction) { - addHandler(element, event, handler, delegationFunction, false); - }, - one(element, event, handler, delegationFunction) { - addHandler(element, event, handler, delegationFunction, true); - }, - off(element, originalTypeEvent, handler, delegationFunction) { - if (typeof originalTypeEvent !== 'string' || !element) { - return; - } - const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction); - const inNamespace = typeEvent !== originalTypeEvent; - const events = getElementEvents(element); - const storeElementEvent = events[typeEvent] || {}; - const isNamespace = originalTypeEvent.startsWith('.'); - if (typeof callable !== 'undefined') { - // Simplest case: handler is passed, remove that listener ONLY. - if (!Object.keys(storeElementEvent).length) { - return; - } - removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null); - return; - } - if (isNamespace) { - for (const elementEvent of Object.keys(events)) { - removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1)); - } - } - for (const [keyHandlers, event] of Object.entries(storeElementEvent)) { - const handlerKey = keyHandlers.replace(stripUidRegex, ''); - if (!inNamespace || originalTypeEvent.includes(handlerKey)) { - removeHandler(element, events, typeEvent, event.callable, event.delegationSelector); - } - } - }, - trigger(element, event, args) { - if (typeof event !== 'string' || !element) { - return null; - } - const $ = getjQuery(); - const typeEvent = getTypeEvent(event); - const inNamespace = event !== typeEvent; - let jQueryEvent = null; - let bubbles = true; - let nativeDispatch = true; - let defaultPrevented = false; - if (inNamespace && $) { - jQueryEvent = $.Event(event, args); - $(element).trigger(jQueryEvent); - bubbles = !jQueryEvent.isPropagationStopped(); - nativeDispatch = !jQueryEvent.isImmediatePropagationStopped(); - defaultPrevented = jQueryEvent.isDefaultPrevented(); - } - const evt = hydrateObj(new Event(event, { - bubbles, - cancelable: true - }), args); - if (defaultPrevented) { - evt.preventDefault(); - } - if (nativeDispatch) { - element.dispatchEvent(evt); - } - if (evt.defaultPrevented && jQueryEvent) { - jQueryEvent.preventDefault(); - } - return evt; - } - }; - function hydrateObj(obj, meta = {}) { - for (const [key, value] of Object.entries(meta)) { - try { - obj[key] = value; - } catch (_unused) { - Object.defineProperty(obj, key, { - configurable: true, - get() { - return value; - } - }); - } + + Carousel.prototype.pause = function (e) { + e || (this.paused = true) + + if (this.$element.find('.next, .prev').length && $.support.transition) { + this.$element.trigger($.support.transition.end) + this.cycle(true) } - return obj; + + this.interval = clearInterval(this.interval) + + return this + } + + Carousel.prototype.next = function () { + if (this.sliding) return + return this.slide('next') } - /** - * -------------------------------------------------------------------------- - * Bootstrap dom/manipulator.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ + Carousel.prototype.prev = function () { + if (this.sliding) return + return this.slide('prev') + } - function normalizeData(value) { - if (value === 'true') { - return true; - } - if (value === 'false') { - return false; - } - if (value === Number(value).toString()) { - return Number(value); - } - if (value === '' || value === 'null') { - return null; - } - if (typeof value !== 'string') { - return value; - } - try { - return JSON.parse(decodeURIComponent(value)); - } catch (_unused) { - return value; + Carousel.prototype.slide = function (type, next) { + var $active = this.$element.find('.item.active') + var $next = next || this.getItemForDirection(type, $active) + var isCycling = this.interval + var direction = type == 'next' ? 'left' : 'right' + var that = this + + if ($next.hasClass('active')) return (this.sliding = false) + + var relatedTarget = $next[0] + var slideEvent = $.Event('slide.bs.carousel', { + relatedTarget: relatedTarget, + direction: direction + }) + this.$element.trigger(slideEvent) + if (slideEvent.isDefaultPrevented()) return + + this.sliding = true + + isCycling && this.pause() + + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) + $nextIndicator && $nextIndicator.addClass('active') + } + + var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" + if ($.support.transition && this.$element.hasClass('slide')) { + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + $active + .one('bsTransitionEnd', function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { + that.$element.trigger(slidEvent) + }, 0) + }) + .emulateTransitionEnd(Carousel.TRANSITION_DURATION) + } else { + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger(slidEvent) } + + isCycling && this.cycle() + + return this } - function normalizeDataKey(key) { - return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`); + + + // CAROUSEL PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.carousel') + var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) + var action = typeof option == 'string' ? option : options.slide + + if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.pause().cycle() + }) } - const Manipulator = { - setDataAttribute(element, key, value) { - element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value); - }, - removeDataAttribute(element, key) { - element.removeAttribute(`data-bs-${normalizeDataKey(key)}`); - }, - getDataAttributes(element) { - if (!element) { - return {}; - } - const attributes = {}; - const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig')); - for (const key of bsKeys) { - let pureKey = key.replace(/^bs/, ''); - pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length); - attributes[pureKey] = normalizeData(element.dataset[key]); - } - return attributes; - }, - getDataAttribute(element, key) { - return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`)); - } - }; - /** - * -------------------------------------------------------------------------- - * Bootstrap util/config.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ + var old = $.fn.carousel + $.fn.carousel = Plugin + $.fn.carousel.Constructor = Carousel - /** - * Class definition - */ - class Config { - // Getters - static get Default() { - return {}; - } - static get DefaultType() { - return {}; - } - static get NAME() { - throw new Error('You have to implement the static method "NAME", for each component!'); - } - _getConfig(config) { - config = this._mergeConfigObj(config); - config = this._configAfterMerge(config); - this._typeCheckConfig(config); - return config; - } - _configAfterMerge(config) { - return config; - } - _mergeConfigObj(config, element) { - const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {}; // try to parse - - return { - ...this.constructor.Default, - ...(typeof jsonConfig === 'object' ? jsonConfig : {}), - ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}), - ...(typeof config === 'object' ? config : {}) - }; - } - _typeCheckConfig(config, configTypes = this.constructor.DefaultType) { - for (const [property, expectedTypes] of Object.entries(configTypes)) { - const value = config[property]; - const valueType = isElement(value) ? 'element' : toType(value); - if (!new RegExp(expectedTypes).test(valueType)) { - throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".`); - } - } + // CAROUSEL NO CONFLICT + // ==================== + + $.fn.carousel.noConflict = function () { + $.fn.carousel = old + return this + } + + + // CAROUSEL DATA-API + // ================= + + var clickHandler = function (e) { + var href + var $this = $(this) + var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 + if (!$target.hasClass('carousel')) return + var options = $.extend({}, $target.data(), $this.data()) + var slideIndex = $this.attr('data-slide-to') + if (slideIndex) options.interval = false + + Plugin.call($target, options) + + if (slideIndex) { + $target.data('bs.carousel').to(slideIndex) } + + e.preventDefault() } - /** - * -------------------------------------------------------------------------- - * Bootstrap base-component.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ + $(document) + .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) + .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) + $(window).on('load', function () { + $('[data-ride="carousel"]').each(function () { + var $carousel = $(this) + Plugin.call($carousel, $carousel.data()) + }) + }) - /** - * Constants - */ +}(jQuery); - const VERSION = '5.3.3'; +/* ======================================================================== + * Bootstrap: collapse.js v3.3.7 + * http://getbootstrap.com/javascript/#collapse + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ - /** - * Class definition - */ +/* jshint latedef: false */ - class BaseComponent extends Config { - constructor(element, config) { - super(); - element = getElement(element); - if (!element) { - return; - } - this._element = element; - this._config = this._getConfig(config); - Data.set(this._element, this.constructor.DATA_KEY, this); - } ++function ($) { + 'use strict'; - // Public - dispose() { - Data.remove(this._element, this.constructor.DATA_KEY); - EventHandler.off(this._element, this.constructor.EVENT_KEY); - for (const propertyName of Object.getOwnPropertyNames(this)) { - this[propertyName] = null; - } - } - _queueCallback(callback, element, isAnimated = true) { - executeAfterTransition(callback, element, isAnimated); - } - _getConfig(config) { - config = this._mergeConfigObj(config, this._element); - config = this._configAfterMerge(config); - this._typeCheckConfig(config); - return config; - } + // COLLAPSE PUBLIC CLASS DEFINITION + // ================================ - // Static - static getInstance(element) { - return Data.get(getElement(element), this.DATA_KEY); - } - static getOrCreateInstance(element, config = {}) { - return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null); - } - static get VERSION() { - return VERSION; - } - static get DATA_KEY() { - return `bs.${this.NAME}`; - } - static get EVENT_KEY() { - return `.${this.DATA_KEY}`; - } - static eventName(name) { - return `${name}${this.EVENT_KEY}`; + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Collapse.DEFAULTS, options) + this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + + '[data-toggle="collapse"][data-target="#' + element.id + '"]') + this.transitioning = null + + if (this.options.parent) { + this.$parent = this.getParent() + } else { + this.addAriaAndCollapsedClass(this.$element, this.$trigger) } + + if (this.options.toggle) this.toggle() } - /** - * -------------------------------------------------------------------------- - * Bootstrap dom/selector-engine.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - - const getSelector = element => { - let selector = element.getAttribute('data-bs-target'); - if (!selector || selector === '#') { - let hrefAttribute = element.getAttribute('href'); - - // The only valid content that could double as a selector are IDs or classes, - // so everything starting with `#` or `.`. If a "real" URL is used as the selector, - // `document.querySelector` will rightfully complain it is invalid. - // See https://github.com/twbs/bootstrap/issues/32273 - if (!hrefAttribute || !hrefAttribute.includes('#') && !hrefAttribute.startsWith('.')) { - return null; - } + Collapse.VERSION = '3.3.7' - // Just in case some CMS puts out a full URL with the anchor appended - if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) { - hrefAttribute = `#${hrefAttribute.split('#')[1]}`; - } - selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null; - } - return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null; - }; - const SelectorEngine = { - find(selector, element = document.documentElement) { - return [].concat(...Element.prototype.querySelectorAll.call(element, selector)); - }, - findOne(selector, element = document.documentElement) { - return Element.prototype.querySelector.call(element, selector); - }, - children(element, selector) { - return [].concat(...element.children).filter(child => child.matches(selector)); - }, - parents(element, selector) { - const parents = []; - let ancestor = element.parentNode.closest(selector); - while (ancestor) { - parents.push(ancestor); - ancestor = ancestor.parentNode.closest(selector); - } - return parents; - }, - prev(element, selector) { - let previous = element.previousElementSibling; - while (previous) { - if (previous.matches(selector)) { - return [previous]; - } - previous = previous.previousElementSibling; - } - return []; - }, - // TODO: this is now unused; remove later along with prev() - next(element, selector) { - let next = element.nextElementSibling; - while (next) { - if (next.matches(selector)) { - return [next]; - } - next = next.nextElementSibling; - } - return []; - }, - focusableChildren(element) { - const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable="true"]'].map(selector => `${selector}:not([tabindex^="-"])`).join(','); - return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el)); - }, - getSelectorFromElement(element) { - const selector = getSelector(element); - if (selector) { - return SelectorEngine.findOne(selector) ? selector : null; - } - return null; - }, - getElementFromSelector(element) { - const selector = getSelector(element); - return selector ? SelectorEngine.findOne(selector) : null; - }, - getMultipleElementsFromSelector(element) { - const selector = getSelector(element); - return selector ? SelectorEngine.find(selector) : []; - } - }; - - /** - * -------------------------------------------------------------------------- - * Bootstrap util/component-functions.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - - const enableDismissTrigger = (component, method = 'hide') => { - const clickEvent = `click.dismiss${component.EVENT_KEY}`; - const name = component.NAME; - EventHandler.on(document, clickEvent, `[data-bs-dismiss="${name}"]`, function (event) { - if (['A', 'AREA'].includes(this.tagName)) { - event.preventDefault(); - } - if (isDisabled(this)) { - return; - } - const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`); - const instance = component.getOrCreateInstance(target); - - // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method - instance[method](); - }); - }; - - /** - * -------------------------------------------------------------------------- - * Bootstrap alert.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - - - /** - * Constants - */ - - const NAME$f = 'alert'; - const DATA_KEY$a = 'bs.alert'; - const EVENT_KEY$b = `.${DATA_KEY$a}`; - const EVENT_CLOSE = `close${EVENT_KEY$b}`; - const EVENT_CLOSED = `closed${EVENT_KEY$b}`; - const CLASS_NAME_FADE$5 = 'fade'; - const CLASS_NAME_SHOW$8 = 'show'; - - /** - * Class definition - */ - - class Alert extends BaseComponent { - // Getters - static get NAME() { - return NAME$f; - } + Collapse.TRANSITION_DURATION = 350 - // Public - close() { - const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE); - if (closeEvent.defaultPrevented) { - return; - } - this._element.classList.remove(CLASS_NAME_SHOW$8); - const isAnimated = this._element.classList.contains(CLASS_NAME_FADE$5); - this._queueCallback(() => this._destroyElement(), this._element, isAnimated); + Collapse.DEFAULTS = { + toggle: true + } + + Collapse.prototype.dimension = function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + Collapse.prototype.show = function () { + if (this.transitioning || this.$element.hasClass('in')) return + + var activesData + var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') + + if (actives && actives.length) { + activesData = actives.data('bs.collapse') + if (activesData && activesData.transitioning) return } - // Private - _destroyElement() { - this._element.remove(); - EventHandler.trigger(this._element, EVENT_CLOSED); - this.dispose(); + var startEvent = $.Event('show.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + if (actives && actives.length) { + Plugin.call(actives, 'hide') + activesData || actives.data('bs.collapse', null) } - // Static - static jQueryInterface(config) { - return this.each(function () { - const data = Alert.getOrCreateInstance(this); - if (typeof config !== 'string') { - return; - } - if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { - throw new TypeError(`No method named "${config}"`); - } - data[config](this); - }); + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + .addClass('collapsing')[dimension](0) + .attr('aria-expanded', true) + + this.$trigger + .removeClass('collapsed') + .attr('aria-expanded', true) + + this.transitioning = 1 + + var complete = function () { + this.$element + .removeClass('collapsing') + .addClass('collapse in')[dimension]('') + this.transitioning = 0 + this.$element + .trigger('shown.bs.collapse') } - } - /** - * Data API implementation - */ + if (!$.support.transition) return complete.call(this) - enableDismissTrigger(Alert, 'close'); + var scrollSize = $.camelCase(['scroll', dimension].join('-')) - /** - * jQuery - */ + this.$element + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) + } - defineJQueryPlugin(Alert); + Collapse.prototype.hide = function () { + if (this.transitioning || !this.$element.hasClass('in')) return - /** - * -------------------------------------------------------------------------- - * Bootstrap button.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ + var startEvent = $.Event('hide.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + var dimension = this.dimension() - /** - * Constants - */ + this.$element[dimension](this.$element[dimension]())[0].offsetHeight - const NAME$e = 'button'; - const DATA_KEY$9 = 'bs.button'; - const EVENT_KEY$a = `.${DATA_KEY$9}`; - const DATA_API_KEY$6 = '.data-api'; - const CLASS_NAME_ACTIVE$3 = 'active'; - const SELECTOR_DATA_TOGGLE$5 = '[data-bs-toggle="button"]'; - const EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`; + this.$element + .addClass('collapsing') + .removeClass('collapse in') + .attr('aria-expanded', false) - /** - * Class definition - */ + this.$trigger + .addClass('collapsed') + .attr('aria-expanded', false) - class Button extends BaseComponent { - // Getters - static get NAME() { - return NAME$e; - } + this.transitioning = 1 - // Public - toggle() { - // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method - this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE$3)); + var complete = function () { + this.transitioning = 0 + this.$element + .removeClass('collapsing') + .addClass('collapse') + .trigger('hidden.bs.collapse') } - // Static - static jQueryInterface(config) { - return this.each(function () { - const data = Button.getOrCreateInstance(this); - if (config === 'toggle') { - data[config](); - } - }); - } + if (!$.support.transition) return complete.call(this) + + this.$element + [dimension](0) + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION) } - /** - * Data API implementation - */ - - EventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_TOGGLE$5, event => { - event.preventDefault(); - const button = event.target.closest(SELECTOR_DATA_TOGGLE$5); - const data = Button.getOrCreateInstance(button); - data.toggle(); - }); - - /** - * jQuery - */ - - defineJQueryPlugin(Button); - - /** - * -------------------------------------------------------------------------- - * Bootstrap util/swipe.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - - - /** - * Constants - */ - - const NAME$d = 'swipe'; - const EVENT_KEY$9 = '.bs.swipe'; - const EVENT_TOUCHSTART = `touchstart${EVENT_KEY$9}`; - const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY$9}`; - const EVENT_TOUCHEND = `touchend${EVENT_KEY$9}`; - const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY$9}`; - const EVENT_POINTERUP = `pointerup${EVENT_KEY$9}`; - const POINTER_TYPE_TOUCH = 'touch'; - const POINTER_TYPE_PEN = 'pen'; - const CLASS_NAME_POINTER_EVENT = 'pointer-event'; - const SWIPE_THRESHOLD = 40; - const Default$c = { - endCallback: null, - leftCallback: null, - rightCallback: null - }; - const DefaultType$c = { - endCallback: '(function|null)', - leftCallback: '(function|null)', - rightCallback: '(function|null)' - }; - - /** - * Class definition - */ - - class Swipe extends Config { - constructor(element, config) { - super(); - this._element = element; - if (!element || !Swipe.isSupported()) { - return; - } - this._config = this._getConfig(config); - this._deltaX = 0; - this._supportPointerEvents = Boolean(window.PointerEvent); - this._initEvents(); - } + Collapse.prototype.toggle = function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } - // Getters - static get Default() { - return Default$c; - } - static get DefaultType() { - return DefaultType$c; - } - static get NAME() { - return NAME$d; - } + Collapse.prototype.getParent = function () { + return $(this.options.parent) + .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') + .each($.proxy(function (i, element) { + var $element = $(element) + this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) + }, this)) + .end() + } - // Public - dispose() { - EventHandler.off(this._element, EVENT_KEY$9); - } + Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { + var isOpen = $element.hasClass('in') - // Private - _start(event) { - if (!this._supportPointerEvents) { - this._deltaX = event.touches[0].clientX; - return; - } - if (this._eventIsPointerPenTouch(event)) { - this._deltaX = event.clientX; - } - } - _end(event) { - if (this._eventIsPointerPenTouch(event)) { - this._deltaX = event.clientX - this._deltaX; - } - this._handleSwipe(); - execute(this._config.endCallback); - } - _move(event) { - this._deltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this._deltaX; - } - _handleSwipe() { - const absDeltaX = Math.abs(this._deltaX); - if (absDeltaX <= SWIPE_THRESHOLD) { - return; - } - const direction = absDeltaX / this._deltaX; - this._deltaX = 0; - if (!direction) { - return; - } - execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback); - } - _initEvents() { - if (this._supportPointerEvents) { - EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event)); - EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event)); - this._element.classList.add(CLASS_NAME_POINTER_EVENT); - } else { - EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event)); - EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event)); - EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event)); - } - } - _eventIsPointerPenTouch(event) { - return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH); - } + $element.attr('aria-expanded', isOpen) + $trigger + .toggleClass('collapsed', !isOpen) + .attr('aria-expanded', isOpen) + } - // Static - static isSupported() { - return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0; - } + function getTargetFromTrigger($trigger) { + var href + var target = $trigger.attr('data-target') + || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 + + return $(target) } - /** - * -------------------------------------------------------------------------- - * Bootstrap carousel.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - - - /** - * Constants - */ - - const NAME$c = 'carousel'; - const DATA_KEY$8 = 'bs.carousel'; - const EVENT_KEY$8 = `.${DATA_KEY$8}`; - const DATA_API_KEY$5 = '.data-api'; - const ARROW_LEFT_KEY$1 = 'ArrowLeft'; - const ARROW_RIGHT_KEY$1 = 'ArrowRight'; - const TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch - - const ORDER_NEXT = 'next'; - const ORDER_PREV = 'prev'; - const DIRECTION_LEFT = 'left'; - const DIRECTION_RIGHT = 'right'; - const EVENT_SLIDE = `slide${EVENT_KEY$8}`; - const EVENT_SLID = `slid${EVENT_KEY$8}`; - const EVENT_KEYDOWN$1 = `keydown${EVENT_KEY$8}`; - const EVENT_MOUSEENTER$1 = `mouseenter${EVENT_KEY$8}`; - const EVENT_MOUSELEAVE$1 = `mouseleave${EVENT_KEY$8}`; - const EVENT_DRAG_START = `dragstart${EVENT_KEY$8}`; - const EVENT_LOAD_DATA_API$3 = `load${EVENT_KEY$8}${DATA_API_KEY$5}`; - const EVENT_CLICK_DATA_API$5 = `click${EVENT_KEY$8}${DATA_API_KEY$5}`; - const CLASS_NAME_CAROUSEL = 'carousel'; - const CLASS_NAME_ACTIVE$2 = 'active'; - const CLASS_NAME_SLIDE = 'slide'; - const CLASS_NAME_END = 'carousel-item-end'; - const CLASS_NAME_START = 'carousel-item-start'; - const CLASS_NAME_NEXT = 'carousel-item-next'; - const CLASS_NAME_PREV = 'carousel-item-prev'; - const SELECTOR_ACTIVE = '.active'; - const SELECTOR_ITEM = '.carousel-item'; - const SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM; - const SELECTOR_ITEM_IMG = '.carousel-item img'; - const SELECTOR_INDICATORS = '.carousel-indicators'; - const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'; - const SELECTOR_DATA_RIDE = '[data-bs-ride="carousel"]'; - const KEY_TO_DIRECTION = { - [ARROW_LEFT_KEY$1]: DIRECTION_RIGHT, - [ARROW_RIGHT_KEY$1]: DIRECTION_LEFT - }; - const Default$b = { - interval: 5000, - keyboard: true, - pause: 'hover', - ride: false, - touch: true, - wrap: true - }; - const DefaultType$b = { - interval: '(number|boolean)', - // TODO:v6 remove boolean support - keyboard: 'boolean', - pause: '(string|boolean)', - ride: '(boolean|string)', - touch: 'boolean', - wrap: 'boolean' - }; - - /** - * Class definition - */ - - class Carousel extends BaseComponent { - constructor(element, config) { - super(element, config); - this._interval = null; - this._activeElement = null; - this._isSliding = false; - this.touchTimeout = null; - this._swipeHelper = null; - this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element); - this._addEventListeners(); - if (this._config.ride === CLASS_NAME_CAROUSEL) { - this.cycle(); - } - } - // Getters - static get Default() { - return Default$b; - } - static get DefaultType() { - return DefaultType$b; - } - static get NAME() { - return NAME$c; - } + // COLLAPSE PLUGIN DEFINITION + // ========================== - // Public - next() { - this._slide(ORDER_NEXT); - } - nextWhenVisible() { - // FIXME TODO use `document.visibilityState` - // Don't call next when the page isn't visible - // or the carousel or its parent isn't visible - if (!document.hidden && isVisible(this._element)) { - this.next(); - } - } - prev() { - this._slide(ORDER_PREV); - } - pause() { - if (this._isSliding) { - triggerTransitionEnd(this._element); - } - this._clearInterval(); - } - cycle() { - this._clearInterval(); - this._updateInterval(); - this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval); - } - _maybeEnableCycle() { - if (!this._config.ride) { - return; - } - if (this._isSliding) { - EventHandler.one(this._element, EVENT_SLID, () => this.cycle()); - return; - } - this.cycle(); + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.collapse') + var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false + if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.collapse + + $.fn.collapse = Plugin + $.fn.collapse.Constructor = Collapse + + + // COLLAPSE NO CONFLICT + // ==================== + + $.fn.collapse.noConflict = function () { + $.fn.collapse = old + return this + } + + + // COLLAPSE DATA-API + // ================= + + $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { + var $this = $(this) + + if (!$this.attr('data-target')) e.preventDefault() + + var $target = getTargetFromTrigger($this) + var data = $target.data('bs.collapse') + var option = data ? 'toggle' : $this.data() + + Plugin.call($target, option) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: dropdown.js v3.3.7 + * http://getbootstrap.com/javascript/#dropdowns + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // DROPDOWN CLASS DEFINITION + // ========================= + + var backdrop = '.dropdown-backdrop' + var toggle = '[data-toggle="dropdown"]' + var Dropdown = function (element) { + $(element).on('click.bs.dropdown', this.toggle) + } + + Dropdown.VERSION = '3.3.7' + + function getParent($this) { + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 } - to(index) { - const items = this._getItems(); - if (index > items.length - 1 || index < 0) { - return; - } - if (this._isSliding) { - EventHandler.one(this._element, EVENT_SLID, () => this.to(index)); - return; - } - const activeIndex = this._getItemIndex(this._getActive()); - if (activeIndex === index) { - return; + + var $parent = selector && $(selector) + + return $parent && $parent.length ? $parent : $this.parent() + } + + function clearMenus(e) { + if (e && e.which === 3) return + $(backdrop).remove() + $(toggle).each(function () { + var $this = $(this) + var $parent = getParent($this) + var relatedTarget = { relatedTarget: this } + + if (!$parent.hasClass('open')) return + + if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return + + $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this.attr('aria-expanded', 'false') + $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget)) + }) + } + + Dropdown.prototype.toggle = function (e) { + var $this = $(this) + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { + // if mobile we use a backdrop because click events don't delegate + $(document.createElement('div')) + .addClass('dropdown-backdrop') + .insertAfter($(this)) + .on('click', clearMenus) } - const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV; - this._slide(order, items[index]); + + var relatedTarget = { relatedTarget: this } + $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this + .trigger('focus') + .attr('aria-expanded', 'true') + + $parent + .toggleClass('open') + .trigger($.Event('shown.bs.dropdown', relatedTarget)) } - dispose() { - if (this._swipeHelper) { - this._swipeHelper.dispose(); - } - super.dispose(); + + return false + } + + Dropdown.prototype.keydown = function (e) { + if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return + + var $this = $(this) + + e.preventDefault() + e.stopPropagation() + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + if (!isActive && e.which != 27 || isActive && e.which == 27) { + if (e.which == 27) $parent.find(toggle).trigger('focus') + return $this.trigger('click') } - // Private - _configAfterMerge(config) { - config.defaultInterval = config.interval; - return config; + var desc = ' li:not(.disabled):visible a' + var $items = $parent.find('.dropdown-menu' + desc) + + if (!$items.length) return + + var index = $items.index(e.target) + + if (e.which == 38 && index > 0) index-- // up + if (e.which == 40 && index < $items.length - 1) index++ // down + if (!~index) index = 0 + + $items.eq(index).trigger('focus') + } + + + // DROPDOWN PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.dropdown') + + if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.dropdown + + $.fn.dropdown = Plugin + $.fn.dropdown.Constructor = Dropdown + + + // DROPDOWN NO CONFLICT + // ==================== + + $.fn.dropdown.noConflict = function () { + $.fn.dropdown = old + return this + } + + + // APPLY TO STANDARD DROPDOWN ELEMENTS + // =================================== + + $(document) + .on('click.bs.dropdown.data-api', clearMenus) + .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) + .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) + .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) + .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: modal.js v3.3.7 + * http://getbootstrap.com/javascript/#modals + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // MODAL CLASS DEFINITION + // ====================== + + var Modal = function (element, options) { + this.options = options + this.$body = $(document.body) + this.$element = $(element) + this.$dialog = this.$element.find('.modal-dialog') + this.$backdrop = null + this.isShown = null + this.originalBodyPad = null + this.scrollbarWidth = 0 + this.ignoreBackdropClick = false + + if (this.options.remote) { + this.$element + .find('.modal-content') + .load(this.options.remote, $.proxy(function () { + this.$element.trigger('loaded.bs.modal') + }, this)) } - _addEventListeners() { - if (this._config.keyboard) { - EventHandler.on(this._element, EVENT_KEYDOWN$1, event => this._keydown(event)); - } - if (this._config.pause === 'hover') { - EventHandler.on(this._element, EVENT_MOUSEENTER$1, () => this.pause()); - EventHandler.on(this._element, EVENT_MOUSELEAVE$1, () => this._maybeEnableCycle()); - } - if (this._config.touch && Swipe.isSupported()) { - this._addTouchEventListeners(); + } + + Modal.VERSION = '3.3.7' + + Modal.TRANSITION_DURATION = 300 + Modal.BACKDROP_TRANSITION_DURATION = 150 + + Modal.DEFAULTS = { + backdrop: true, + keyboard: true, + show: true + } + + Modal.prototype.toggle = function (_relatedTarget) { + return this.isShown ? this.hide() : this.show(_relatedTarget) + } + + Modal.prototype.show = function (_relatedTarget) { + var that = this + var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) + + this.$element.trigger(e) + + if (this.isShown || e.isDefaultPrevented()) return + + this.isShown = true + + this.checkScrollbar() + this.setScrollbar() + this.$body.addClass('modal-open') + + this.escape() + this.resize() + + this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) + + this.$dialog.on('mousedown.dismiss.bs.modal', function () { + that.$element.one('mouseup.dismiss.bs.modal', function (e) { + if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true + }) + }) + + this.backdrop(function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + if (!that.$element.parent().length) { + that.$element.appendTo(that.$body) // don't move modals dom position } - } - _addTouchEventListeners() { - for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) { - EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault()); + + that.$element + .show() + .scrollTop(0) + + that.adjustDialog() + + if (transition) { + that.$element[0].offsetWidth // force reflow } - const endCallBack = () => { - if (this._config.pause !== 'hover') { - return; - } - // If it's a touch-enabled device, mouseenter/leave are fired as - // part of the mouse compatibility events on first tap - the carousel - // would stop cycling until user tapped out of it; - // here, we listen for touchend, explicitly pause the carousel - // (as if it's the second time we tap on it, mouseenter compat event - // is NOT fired) and after a timeout (to allow for mouse compatibility - // events to fire) we explicitly restart cycling - - this.pause(); - if (this.touchTimeout) { - clearTimeout(this.touchTimeout); + that.$element.addClass('in') + + that.enforceFocus() + + var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) + + transition ? + that.$dialog // wait for modal to slide in + .one('bsTransitionEnd', function () { + that.$element.trigger('focus').trigger(e) + }) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + that.$element.trigger('focus').trigger(e) + }) + } + + Modal.prototype.hide = function (e) { + if (e) e.preventDefault() + + e = $.Event('hide.bs.modal') + + this.$element.trigger(e) + + if (!this.isShown || e.isDefaultPrevented()) return + + this.isShown = false + + this.escape() + this.resize() + + $(document).off('focusin.bs.modal') + + this.$element + .removeClass('in') + .off('click.dismiss.bs.modal') + .off('mouseup.dismiss.bs.modal') + + this.$dialog.off('mousedown.dismiss.bs.modal') + + $.support.transition && this.$element.hasClass('fade') ? + this.$element + .one('bsTransitionEnd', $.proxy(this.hideModal, this)) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + this.hideModal() + } + + Modal.prototype.enforceFocus = function () { + $(document) + .off('focusin.bs.modal') // guard against infinite focus loop + .on('focusin.bs.modal', $.proxy(function (e) { + if (document !== e.target && + this.$element[0] !== e.target && + !this.$element.has(e.target).length) { + this.$element.trigger('focus') } - this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval); - }; - const swipeConfig = { - leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)), - rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)), - endCallback: endCallBack - }; - this._swipeHelper = new Swipe(this._element, swipeConfig); - } - _keydown(event) { - if (/input|textarea/i.test(event.target.tagName)) { - return; - } - const direction = KEY_TO_DIRECTION[event.key]; - if (direction) { - event.preventDefault(); - this._slide(this._directionToOrder(direction)); - } - } - _getItemIndex(element) { - return this._getItems().indexOf(element); - } - _setActiveIndicatorElement(index) { - if (!this._indicatorsElement) { - return; - } - const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement); - activeIndicator.classList.remove(CLASS_NAME_ACTIVE$2); - activeIndicator.removeAttribute('aria-current'); - const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to="${index}"]`, this._indicatorsElement); - if (newActiveIndicator) { - newActiveIndicator.classList.add(CLASS_NAME_ACTIVE$2); - newActiveIndicator.setAttribute('aria-current', 'true'); - } + }, this)) + } + + Modal.prototype.escape = function () { + if (this.isShown && this.options.keyboard) { + this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { + e.which == 27 && this.hide() + }, this)) + } else if (!this.isShown) { + this.$element.off('keydown.dismiss.bs.modal') } - _updateInterval() { - const element = this._activeElement || this._getActive(); - if (!element) { - return; - } - const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10); - this._config.interval = elementInterval || this._config.defaultInterval; + } + + Modal.prototype.resize = function () { + if (this.isShown) { + $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) + } else { + $(window).off('resize.bs.modal') } - _slide(order, element = null) { - if (this._isSliding) { - return; - } - const activeElement = this._getActive(); - const isNext = order === ORDER_NEXT; - const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap); - if (nextElement === activeElement) { - return; - } - const nextElementIndex = this._getItemIndex(nextElement); - const triggerEvent = eventName => { - return EventHandler.trigger(this._element, eventName, { - relatedTarget: nextElement, - direction: this._orderToDirection(order), - from: this._getItemIndex(activeElement), - to: nextElementIndex - }); - }; - const slideEvent = triggerEvent(EVENT_SLIDE); - if (slideEvent.defaultPrevented) { - return; - } - if (!activeElement || !nextElement) { - // Some weirdness is happening, so we bail - // TODO: change tests that use empty divs to avoid this check - return; - } - const isCycling = Boolean(this._interval); - this.pause(); - this._isSliding = true; - this._setActiveIndicatorElement(nextElementIndex); - this._activeElement = nextElement; - const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END; - const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV; - nextElement.classList.add(orderClassName); - reflow(nextElement); - activeElement.classList.add(directionalClassName); - nextElement.classList.add(directionalClassName); - const completeCallBack = () => { - nextElement.classList.remove(directionalClassName, orderClassName); - nextElement.classList.add(CLASS_NAME_ACTIVE$2); - activeElement.classList.remove(CLASS_NAME_ACTIVE$2, orderClassName, directionalClassName); - this._isSliding = false; - triggerEvent(EVENT_SLID); - }; - this._queueCallback(completeCallBack, activeElement, this._isAnimated()); - if (isCycling) { - this.cycle(); + } + + Modal.prototype.hideModal = function () { + var that = this + this.$element.hide() + this.backdrop(function () { + that.$body.removeClass('modal-open') + that.resetAdjustments() + that.resetScrollbar() + that.$element.trigger('hidden.bs.modal') + }) + } + + Modal.prototype.removeBackdrop = function () { + this.$backdrop && this.$backdrop.remove() + this.$backdrop = null + } + + Modal.prototype.backdrop = function (callback) { + var that = this + var animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $(document.createElement('div')) + .addClass('modal-backdrop ' + animate) + .appendTo(this.$body) + + this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { + if (this.ignoreBackdropClick) { + this.ignoreBackdropClick = false + return + } + if (e.target !== e.currentTarget) return + this.options.backdrop == 'static' + ? this.$element[0].focus() + : this.hide() + }, this)) + + if (doAnimate) this.$backdrop[0].offsetWidth // force reflow + + this.$backdrop.addClass('in') + + if (!callback) return + + doAnimate ? + this.$backdrop + .one('bsTransitionEnd', callback) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callback() + + } else if (!this.isShown && this.$backdrop) { + this.$backdrop.removeClass('in') + + var callbackRemove = function () { + that.removeBackdrop() + callback && callback() } + $.support.transition && this.$element.hasClass('fade') ? + this.$backdrop + .one('bsTransitionEnd', callbackRemove) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callbackRemove() + + } else if (callback) { + callback() } - _isAnimated() { - return this._element.classList.contains(CLASS_NAME_SLIDE); - } - _getActive() { - return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element); + } + + // these following methods are used to handle overflowing modals + + Modal.prototype.handleUpdate = function () { + this.adjustDialog() + } + + Modal.prototype.adjustDialog = function () { + var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight + + this.$element.css({ + paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', + paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' + }) + } + + Modal.prototype.resetAdjustments = function () { + this.$element.css({ + paddingLeft: '', + paddingRight: '' + }) + } + + Modal.prototype.checkScrollbar = function () { + var fullWindowWidth = window.innerWidth + if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 + var documentElementRect = document.documentElement.getBoundingClientRect() + fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) } - _getItems() { - return SelectorEngine.find(SELECTOR_ITEM, this._element); + this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth + this.scrollbarWidth = this.measureScrollbar() + } + + Modal.prototype.setScrollbar = function () { + var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) + this.originalBodyPad = document.body.style.paddingRight || '' + if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) + } + + Modal.prototype.resetScrollbar = function () { + this.$body.css('padding-right', this.originalBodyPad) + } + + Modal.prototype.measureScrollbar = function () { // thx walsh + var scrollDiv = document.createElement('div') + scrollDiv.className = 'modal-scrollbar-measure' + this.$body.append(scrollDiv) + var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth + this.$body[0].removeChild(scrollDiv) + return scrollbarWidth + } + + + // MODAL PLUGIN DEFINITION + // ======================= + + function Plugin(option, _relatedTarget) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.modal') + var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data) $this.data('bs.modal', (data = new Modal(this, options))) + if (typeof option == 'string') data[option](_relatedTarget) + else if (options.show) data.show(_relatedTarget) + }) + } + + var old = $.fn.modal + + $.fn.modal = Plugin + $.fn.modal.Constructor = Modal + + + // MODAL NO CONFLICT + // ================= + + $.fn.modal.noConflict = function () { + $.fn.modal = old + return this + } + + + // MODAL DATA-API + // ============== + + $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { + var $this = $(this) + var href = $this.attr('href') + var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 + var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) + + if ($this.is('a')) e.preventDefault() + + $target.one('show.bs.modal', function (showEvent) { + if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown + $target.one('hidden.bs.modal', function () { + $this.is(':visible') && $this.trigger('focus') + }) + }) + Plugin.call($target, option, this) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tooltip.js v3.3.7 + * http://getbootstrap.com/javascript/#tooltip + * Inspired by the original jQuery.tipsy by Jason Frame + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TOOLTIP PUBLIC CLASS DEFINITION + // =============================== + + var Tooltip = function (element, options) { + this.type = null + this.options = null + this.enabled = null + this.timeout = null + this.hoverState = null + this.$element = null + this.inState = null + + this.init('tooltip', element, options) + } + + Tooltip.VERSION = '3.3.7' + + Tooltip.TRANSITION_DURATION = 150 + + Tooltip.DEFAULTS = { + animation: true, + placement: 'top', + selector: false, + template: '', + trigger: 'hover focus', + title: '', + delay: 0, + html: false, + container: false, + viewport: { + selector: 'body', + padding: 0 } - _clearInterval() { - if (this._interval) { - clearInterval(this._interval); - this._interval = null; - } + } + + Tooltip.prototype.init = function (type, element, options) { + this.enabled = true + this.type = type + this.$element = $(element) + this.options = this.getOptions(options) + this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)) + this.inState = { click: false, hover: false, focus: false } + + if (this.$element[0] instanceof document.constructor && !this.options.selector) { + throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!') } - _directionToOrder(direction) { - if (isRTL()) { - return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT; + + var triggers = this.options.trigger.split(' ') + + for (var i = triggers.length; i--;) { + var trigger = triggers[i] + + if (trigger == 'click') { + this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) + } else if (trigger != 'manual') { + var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' + var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' + + this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) + this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) } - return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV; } - _orderToDirection(order) { - if (isRTL()) { - return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT; + + this.options.selector ? + (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : + this.fixTitle() + } + + Tooltip.prototype.getDefaults = function () { + return Tooltip.DEFAULTS + } + + Tooltip.prototype.getOptions = function (options) { + options = $.extend({}, this.getDefaults(), this.$element.data(), options) + + if (options.delay && typeof options.delay == 'number') { + options.delay = { + show: options.delay, + hide: options.delay } - return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT; } - // Static - static jQueryInterface(config) { - return this.each(function () { - const data = Carousel.getOrCreateInstance(this, config); - if (typeof config === 'number') { - data.to(config); - return; - } - if (typeof config === 'string') { - if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { - throw new TypeError(`No method named "${config}"`); - } - data[config](); - } - }); - } + return options } - /** - * Data API implementation - */ + Tooltip.prototype.getDelegateOptions = function () { + var options = {} + var defaults = this.getDefaults() - EventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_SLIDE, function (event) { - const target = SelectorEngine.getElementFromSelector(this); - if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) { - return; - } - event.preventDefault(); - const carousel = Carousel.getOrCreateInstance(target); - const slideIndex = this.getAttribute('data-bs-slide-to'); - if (slideIndex) { - carousel.to(slideIndex); - carousel._maybeEnableCycle(); - return; - } - if (Manipulator.getDataAttribute(this, 'slide') === 'next') { - carousel.next(); - carousel._maybeEnableCycle(); - return; - } - carousel.prev(); - carousel._maybeEnableCycle(); - }); - EventHandler.on(window, EVENT_LOAD_DATA_API$3, () => { - const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE); - for (const carousel of carousels) { - Carousel.getOrCreateInstance(carousel); + this._options && $.each(this._options, function (key, value) { + if (defaults[key] != value) options[key] = value + }) + + return options + } + + Tooltip.prototype.enter = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) } - }); - - /** - * jQuery - */ - - defineJQueryPlugin(Carousel); - - /** - * -------------------------------------------------------------------------- - * Bootstrap collapse.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - - - /** - * Constants - */ - - const NAME$b = 'collapse'; - const DATA_KEY$7 = 'bs.collapse'; - const EVENT_KEY$7 = `.${DATA_KEY$7}`; - const DATA_API_KEY$4 = '.data-api'; - const EVENT_SHOW$6 = `show${EVENT_KEY$7}`; - const EVENT_SHOWN$6 = `shown${EVENT_KEY$7}`; - const EVENT_HIDE$6 = `hide${EVENT_KEY$7}`; - const EVENT_HIDDEN$6 = `hidden${EVENT_KEY$7}`; - const EVENT_CLICK_DATA_API$4 = `click${EVENT_KEY$7}${DATA_API_KEY$4}`; - const CLASS_NAME_SHOW$7 = 'show'; - const CLASS_NAME_COLLAPSE = 'collapse'; - const CLASS_NAME_COLLAPSING = 'collapsing'; - const CLASS_NAME_COLLAPSED = 'collapsed'; - const CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`; - const CLASS_NAME_HORIZONTAL = 'collapse-horizontal'; - const WIDTH = 'width'; - const HEIGHT = 'height'; - const SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'; - const SELECTOR_DATA_TOGGLE$4 = '[data-bs-toggle="collapse"]'; - const Default$a = { - parent: null, - toggle: true - }; - const DefaultType$a = { - parent: '(null|element)', - toggle: 'boolean' - }; - - /** - * Class definition - */ - - class Collapse extends BaseComponent { - constructor(element, config) { - super(element, config); - this._isTransitioning = false; - this._triggerArray = []; - const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$4); - for (const elem of toggleList) { - const selector = SelectorEngine.getSelectorFromElement(elem); - const filterElement = SelectorEngine.find(selector).filter(foundElement => foundElement === this._element); - if (selector !== null && filterElement.length) { - this._triggerArray.push(elem); - } - } - this._initializeChildren(); - if (!this._config.parent) { - this._addAriaAndCollapsedClass(this._triggerArray, this._isShown()); - } - if (this._config.toggle) { - this.toggle(); - } + + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true } - // Getters - static get Default() { - return Default$a; + if (self.tip().hasClass('in') || self.hoverState == 'in') { + self.hoverState = 'in' + return } - static get DefaultType() { - return DefaultType$a; + + clearTimeout(self.timeout) + + self.hoverState = 'in' + + if (!self.options.delay || !self.options.delay.show) return self.show() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'in') self.show() + }, self.options.delay.show) + } + + Tooltip.prototype.isInStateTrue = function () { + for (var key in this.inState) { + if (this.inState[key]) return true } - static get NAME() { - return NAME$b; + + return false + } + + Tooltip.prototype.leave = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) } - // Public - toggle() { - if (this._isShown()) { - this.hide(); - } else { - this.show(); - } + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false } - show() { - if (this._isTransitioning || this._isShown()) { - return; - } - let activeChildren = []; - // find active children - if (this._config.parent) { - activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES).filter(element => element !== this._element).map(element => Collapse.getOrCreateInstance(element, { - toggle: false - })); - } - if (activeChildren.length && activeChildren[0]._isTransitioning) { - return; - } - const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$6); - if (startEvent.defaultPrevented) { - return; + if (self.isInStateTrue()) return + + clearTimeout(self.timeout) + + self.hoverState = 'out' + + if (!self.options.delay || !self.options.delay.hide) return self.hide() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'out') self.hide() + }, self.options.delay.hide) + } + + Tooltip.prototype.show = function () { + var e = $.Event('show.bs.' + this.type) + + if (this.hasContent() && this.enabled) { + this.$element.trigger(e) + + var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) + if (e.isDefaultPrevented() || !inDom) return + var that = this + + var $tip = this.tip() + + var tipId = this.getUID(this.type) + + this.setContent() + $tip.attr('id', tipId) + this.$element.attr('aria-describedby', tipId) + + if (this.options.animation) $tip.addClass('fade') + + var placement = typeof this.options.placement == 'function' ? + this.options.placement.call(this, $tip[0], this.$element[0]) : + this.options.placement + + var autoToken = /\s?auto?\s?/i + var autoPlace = autoToken.test(placement) + if (autoPlace) placement = placement.replace(autoToken, '') || 'top' + + $tip + .detach() + .css({ top: 0, left: 0, display: 'block' }) + .addClass(placement) + .data('bs.' + this.type, this) + + this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) + this.$element.trigger('inserted.bs.' + this.type) + + var pos = this.getPosition() + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (autoPlace) { + var orgPlacement = placement + var viewportDim = this.getPosition(this.$viewport) + + placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' : + placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' : + placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' : + placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' : + placement + + $tip + .removeClass(orgPlacement) + .addClass(placement) } - for (const activeInstance of activeChildren) { - activeInstance.hide(); + + var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) + + this.applyPlacement(calculatedOffset, placement) + + var complete = function () { + var prevHoverState = that.hoverState + that.$element.trigger('shown.bs.' + that.type) + that.hoverState = null + + if (prevHoverState == 'out') that.leave(that) } - const dimension = this._getDimension(); - this._element.classList.remove(CLASS_NAME_COLLAPSE); - this._element.classList.add(CLASS_NAME_COLLAPSING); - this._element.style[dimension] = 0; - this._addAriaAndCollapsedClass(this._triggerArray, true); - this._isTransitioning = true; - const complete = () => { - this._isTransitioning = false; - this._element.classList.remove(CLASS_NAME_COLLAPSING); - this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7); - this._element.style[dimension] = ''; - EventHandler.trigger(this._element, EVENT_SHOWN$6); - }; - const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1); - const scrollSize = `scroll${capitalizedDimension}`; - this._queueCallback(complete, this._element, true); - this._element.style[dimension] = `${this._element[scrollSize]}px`; + + $.support.transition && this.$tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() } - hide() { - if (this._isTransitioning || !this._isShown()) { - return; - } - const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$6); - if (startEvent.defaultPrevented) { - return; - } - const dimension = this._getDimension(); - this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`; - reflow(this._element); - this._element.classList.add(CLASS_NAME_COLLAPSING); - this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7); - for (const trigger of this._triggerArray) { - const element = SelectorEngine.getElementFromSelector(trigger); - if (element && !this._isShown(element)) { - this._addAriaAndCollapsedClass([trigger], false); - } + } + + Tooltip.prototype.applyPlacement = function (offset, placement) { + var $tip = this.tip() + var width = $tip[0].offsetWidth + var height = $tip[0].offsetHeight + + // manually read margins because getBoundingClientRect includes difference + var marginTop = parseInt($tip.css('margin-top'), 10) + var marginLeft = parseInt($tip.css('margin-left'), 10) + + // we must check for NaN for ie 8/9 + if (isNaN(marginTop)) marginTop = 0 + if (isNaN(marginLeft)) marginLeft = 0 + + offset.top += marginTop + offset.left += marginLeft + + // $.fn.offset doesn't round pixel values + // so we use setOffset directly with our own function B-0 + $.offset.setOffset($tip[0], $.extend({ + using: function (props) { + $tip.css({ + top: Math.round(props.top), + left: Math.round(props.left) + }) } - this._isTransitioning = true; - const complete = () => { - this._isTransitioning = false; - this._element.classList.remove(CLASS_NAME_COLLAPSING); - this._element.classList.add(CLASS_NAME_COLLAPSE); - EventHandler.trigger(this._element, EVENT_HIDDEN$6); - }; - this._element.style[dimension] = ''; - this._queueCallback(complete, this._element, true); - } - _isShown(element = this._element) { - return element.classList.contains(CLASS_NAME_SHOW$7); - } + }, offset), 0) - // Private - _configAfterMerge(config) { - config.toggle = Boolean(config.toggle); // Coerce string values - config.parent = getElement(config.parent); - return config; - } - _getDimension() { - return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT; + $tip.addClass('in') + + // check to see if placing tip in new offset caused the tip to resize itself + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (placement == 'top' && actualHeight != height) { + offset.top = offset.top + height - actualHeight } - _initializeChildren() { - if (!this._config.parent) { - return; - } - const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE$4); - for (const element of children) { - const selected = SelectorEngine.getElementFromSelector(element); - if (selected) { - this._addAriaAndCollapsedClass([element], this._isShown(selected)); - } + + var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) + + if (delta.left) offset.left += delta.left + else offset.top += delta.top + + var isVertical = /top|bottom/.test(placement) + var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight + var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' + + $tip.offset(offset) + this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) + } + + Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { + this.arrow() + .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') + .css(isVertical ? 'top' : 'left', '') + } + + Tooltip.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + + $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) + $tip.removeClass('fade in top bottom left right') + } + + Tooltip.prototype.hide = function (callback) { + var that = this + var $tip = $(this.$tip) + var e = $.Event('hide.bs.' + this.type) + + function complete() { + if (that.hoverState != 'in') $tip.detach() + if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary. + that.$element + .removeAttr('aria-describedby') + .trigger('hidden.bs.' + that.type) } + callback && callback() } - _getFirstLevelChildren(selector) { - const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent); - // remove children if greater depth - return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element)); - } - _addAriaAndCollapsedClass(triggerArray, isOpen) { - if (!triggerArray.length) { - return; - } - for (const element of triggerArray) { - element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen); - element.setAttribute('aria-expanded', isOpen); - } + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + $tip.removeClass('in') + + $.support.transition && $tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + + this.hoverState = null + + return this + } + + Tooltip.prototype.fixTitle = function () { + var $e = this.$element + if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') { + $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') } + } + + Tooltip.prototype.hasContent = function () { + return this.getTitle() + } + + Tooltip.prototype.getPosition = function ($element) { + $element = $element || this.$element - // Static - static jQueryInterface(config) { - const _config = {}; - if (typeof config === 'string' && /show|hide/.test(config)) { - _config.toggle = false; - } - return this.each(function () { - const data = Collapse.getOrCreateInstance(this, _config); - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`); - } - data[config](); - } - }); + var el = $element[0] + var isBody = el.tagName == 'BODY' + + var elRect = el.getBoundingClientRect() + if (elRect.width == null) { + // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 + elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) } + var isSvg = window.SVGElement && el instanceof window.SVGElement + // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3. + // See https://github.com/twbs/bootstrap/issues/20280 + var elOffset = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset()) + var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } + var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null + + return $.extend({}, elRect, scroll, outerDims, elOffset) } - /** - * Data API implementation - */ + Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { + return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : + /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } - EventHandler.on(document, EVENT_CLICK_DATA_API$4, SELECTOR_DATA_TOGGLE$4, function (event) { - // preventDefault only for elements (which change the URL) not inside the collapsible element - if (event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') { - event.preventDefault(); - } - for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) { - Collapse.getOrCreateInstance(element, { - toggle: false - }).toggle(); - } - }); - - /** - * jQuery - */ - - defineJQueryPlugin(Collapse); - - /** - * -------------------------------------------------------------------------- - * Bootstrap dropdown.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - - - /** - * Constants - */ - - const NAME$a = 'dropdown'; - const DATA_KEY$6 = 'bs.dropdown'; - const EVENT_KEY$6 = `.${DATA_KEY$6}`; - const DATA_API_KEY$3 = '.data-api'; - const ESCAPE_KEY$2 = 'Escape'; - const TAB_KEY$1 = 'Tab'; - const ARROW_UP_KEY$1 = 'ArrowUp'; - const ARROW_DOWN_KEY$1 = 'ArrowDown'; - const RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button - - const EVENT_HIDE$5 = `hide${EVENT_KEY$6}`; - const EVENT_HIDDEN$5 = `hidden${EVENT_KEY$6}`; - const EVENT_SHOW$5 = `show${EVENT_KEY$6}`; - const EVENT_SHOWN$5 = `shown${EVENT_KEY$6}`; - const EVENT_CLICK_DATA_API$3 = `click${EVENT_KEY$6}${DATA_API_KEY$3}`; - const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY$6}${DATA_API_KEY$3}`; - const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY$6}${DATA_API_KEY$3}`; - const CLASS_NAME_SHOW$6 = 'show'; - const CLASS_NAME_DROPUP = 'dropup'; - const CLASS_NAME_DROPEND = 'dropend'; - const CLASS_NAME_DROPSTART = 'dropstart'; - const CLASS_NAME_DROPUP_CENTER = 'dropup-center'; - const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'; - const SELECTOR_DATA_TOGGLE$3 = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)'; - const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE$3}.${CLASS_NAME_SHOW$6}`; - const SELECTOR_MENU = '.dropdown-menu'; - const SELECTOR_NAVBAR = '.navbar'; - const SELECTOR_NAVBAR_NAV = '.navbar-nav'; - const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'; - const PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'; - const PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'; - const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'; - const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'; - const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'; - const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'; - const PLACEMENT_TOPCENTER = 'top'; - const PLACEMENT_BOTTOMCENTER = 'bottom'; - const Default$9 = { - autoClose: true, - boundary: 'clippingParents', - display: 'dynamic', - offset: [0, 2], - popperConfig: null, - reference: 'toggle' - }; - const DefaultType$9 = { - autoClose: '(boolean|string)', - boundary: '(string|element)', - display: 'string', - offset: '(array|string|function)', - popperConfig: '(null|object|function)', - reference: '(string|element|object)' - }; - - /** - * Class definition - */ - - class Dropdown extends BaseComponent { - constructor(element, config) { - super(element, config); - this._popper = null; - this._parent = this._element.parentNode; // dropdown wrapper - // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/ - this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent); - this._inNavbar = this._detectNavbar(); - } + } - // Getters - static get Default() { - return Default$9; - } - static get DefaultType() { - return DefaultType$9; - } - static get NAME() { - return NAME$a; - } + Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { + var delta = { top: 0, left: 0 } + if (!this.$viewport) return delta - // Public - toggle() { - return this._isShown() ? this.hide() : this.show(); - } - show() { - if (isDisabled(this._element) || this._isShown()) { - return; - } - const relatedTarget = { - relatedTarget: this._element - }; - const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$5, relatedTarget); - if (showEvent.defaultPrevented) { - return; - } - this._createPopper(); - - // If this is a touch-enabled device we add extra - // empty mouseover listeners to the body's immediate children; - // only needed because of broken event delegation on iOS - // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html - if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) { - for (const element of [].concat(...document.body.children)) { - EventHandler.on(element, 'mouseover', noop); - } - } - this._element.focus(); - this._element.setAttribute('aria-expanded', true); - this._menu.classList.add(CLASS_NAME_SHOW$6); - this._element.classList.add(CLASS_NAME_SHOW$6); - EventHandler.trigger(this._element, EVENT_SHOWN$5, relatedTarget); - } - hide() { - if (isDisabled(this._element) || !this._isShown()) { - return; - } - const relatedTarget = { - relatedTarget: this._element - }; - this._completeHide(relatedTarget); - } - dispose() { - if (this._popper) { - this._popper.destroy(); + var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 + var viewportDimensions = this.getPosition(this.$viewport) + + if (/right|left/.test(placement)) { + var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll + var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight + if (topEdgeOffset < viewportDimensions.top) { // top overflow + delta.top = viewportDimensions.top - topEdgeOffset + } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow + delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset } - super.dispose(); - } - update() { - this._inNavbar = this._detectNavbar(); - if (this._popper) { - this._popper.update(); + } else { + var leftEdgeOffset = pos.left - viewportPadding + var rightEdgeOffset = pos.left + viewportPadding + actualWidth + if (leftEdgeOffset < viewportDimensions.left) { // left overflow + delta.left = viewportDimensions.left - leftEdgeOffset + } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow + delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset } } - // Private - _completeHide(relatedTarget) { - const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$5, relatedTarget); - if (hideEvent.defaultPrevented) { - return; - } + return delta + } - // If this is a touch-enabled device we remove the extra - // empty mouseover listeners we added for iOS support - if ('ontouchstart' in document.documentElement) { - for (const element of [].concat(...document.body.children)) { - EventHandler.off(element, 'mouseover', noop); - } - } - if (this._popper) { - this._popper.destroy(); - } - this._menu.classList.remove(CLASS_NAME_SHOW$6); - this._element.classList.remove(CLASS_NAME_SHOW$6); - this._element.setAttribute('aria-expanded', 'false'); - Manipulator.removeDataAttribute(this._menu, 'popper'); - EventHandler.trigger(this._element, EVENT_HIDDEN$5, relatedTarget); - } - _getConfig(config) { - config = super._getConfig(config); - if (typeof config.reference === 'object' && !isElement(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') { - // Popper virtual elements require a getBoundingClientRect method - throw new TypeError(`${NAME$a.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`); - } - return config; - } - _createPopper() { - if (typeof Popper__namespace === 'undefined') { - throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)'); - } - let referenceElement = this._element; - if (this._config.reference === 'parent') { - referenceElement = this._parent; - } else if (isElement(this._config.reference)) { - referenceElement = getElement(this._config.reference); - } else if (typeof this._config.reference === 'object') { - referenceElement = this._config.reference; - } - const popperConfig = this._getPopperConfig(); - this._popper = Popper__namespace.createPopper(referenceElement, this._menu, popperConfig); - } - _isShown() { - return this._menu.classList.contains(CLASS_NAME_SHOW$6); - } - _getPlacement() { - const parentDropdown = this._parent; - if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) { - return PLACEMENT_RIGHT; - } - if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) { - return PLACEMENT_LEFT; - } - if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) { - return PLACEMENT_TOPCENTER; - } - if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) { - return PLACEMENT_BOTTOMCENTER; - } + Tooltip.prototype.getTitle = function () { + var title + var $e = this.$element + var o = this.options - // We need to trim the value because custom properties can also include spaces - const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'; - if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) { - return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP; - } - return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM; - } - _detectNavbar() { - return this._element.closest(SELECTOR_NAVBAR) !== null; - } - _getOffset() { - const { - offset - } = this._config; - if (typeof offset === 'string') { - return offset.split(',').map(value => Number.parseInt(value, 10)); - } - if (typeof offset === 'function') { - return popperData => offset(popperData, this._element); - } - return offset; - } - _getPopperConfig() { - const defaultBsPopperConfig = { - placement: this._getPlacement(), - modifiers: [{ - name: 'preventOverflow', - options: { - boundary: this._config.boundary - } - }, { - name: 'offset', - options: { - offset: this._getOffset() - } - }] - }; - - // Disable Popper if we have a static display or Dropdown is in Navbar - if (this._inNavbar || this._config.display === 'static') { - Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // TODO: v6 remove - defaultBsPopperConfig.modifiers = [{ - name: 'applyStyles', - enabled: false - }]; - } - return { - ...defaultBsPopperConfig, - ...execute(this._config.popperConfig, [defaultBsPopperConfig]) - }; - } - _selectMenuItem({ - key, - target - }) { - const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element)); - if (!items.length) { - return; - } + title = $e.attr('data-original-title') + || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) - // if target isn't included in items (e.g. when expanding the dropdown) - // allow cycling to get the last item in case key equals ARROW_UP_KEY - getNextActiveElement(items, target, key === ARROW_DOWN_KEY$1, !items.includes(target)).focus(); - } + return title + } - // Static - static jQueryInterface(config) { - return this.each(function () { - const data = Dropdown.getOrCreateInstance(this, config); - if (typeof config !== 'string') { - return; - } - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`); - } - data[config](); - }); - } - static clearMenus(event) { - if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY$1) { - return; - } - const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN); - for (const toggle of openToggles) { - const context = Dropdown.getInstance(toggle); - if (!context || context._config.autoClose === false) { - continue; - } - const composedPath = event.composedPath(); - const isMenuTarget = composedPath.includes(context._menu); - if (composedPath.includes(context._element) || context._config.autoClose === 'inside' && !isMenuTarget || context._config.autoClose === 'outside' && isMenuTarget) { - continue; - } + Tooltip.prototype.getUID = function (prefix) { + do prefix += ~~(Math.random() * 1000000) + while (document.getElementById(prefix)) + return prefix + } - // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu - if (context._menu.contains(event.target) && (event.type === 'keyup' && event.key === TAB_KEY$1 || /input|select|option|textarea|form/i.test(event.target.tagName))) { - continue; - } - const relatedTarget = { - relatedTarget: context._element - }; - if (event.type === 'click') { - relatedTarget.clickEvent = event; - } - context._completeHide(relatedTarget); - } - } - static dataApiKeydownHandler(event) { - // If not an UP | DOWN | ESCAPE key => not a dropdown command - // If input/textarea && if key is other than ESCAPE => not a dropdown command - - const isInput = /input|textarea/i.test(event.target.tagName); - const isEscapeEvent = event.key === ESCAPE_KEY$2; - const isUpOrDownEvent = [ARROW_UP_KEY$1, ARROW_DOWN_KEY$1].includes(event.key); - if (!isUpOrDownEvent && !isEscapeEvent) { - return; - } - if (isInput && !isEscapeEvent) { - return; - } - event.preventDefault(); - - // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/ - const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE$3) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE$3, event.delegateTarget.parentNode); - const instance = Dropdown.getOrCreateInstance(getToggleButton); - if (isUpOrDownEvent) { - event.stopPropagation(); - instance.show(); - instance._selectMenuItem(event); - return; - } - if (instance._isShown()) { - // else is escape and we check if it is shown - event.stopPropagation(); - instance.hide(); - getToggleButton.focus(); + Tooltip.prototype.tip = function () { + if (!this.$tip) { + this.$tip = $(this.options.template) + if (this.$tip.length != 1) { + throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!') } } + return this.$tip } - /** - * Data API implementation - */ - - EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE$3, Dropdown.dataApiKeydownHandler); - EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler); - EventHandler.on(document, EVENT_CLICK_DATA_API$3, Dropdown.clearMenus); - EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus); - EventHandler.on(document, EVENT_CLICK_DATA_API$3, SELECTOR_DATA_TOGGLE$3, function (event) { - event.preventDefault(); - Dropdown.getOrCreateInstance(this).toggle(); - }); - - /** - * jQuery - */ - - defineJQueryPlugin(Dropdown); - - /** - * -------------------------------------------------------------------------- - * Bootstrap util/backdrop.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - - - /** - * Constants - */ - - const NAME$9 = 'backdrop'; - const CLASS_NAME_FADE$4 = 'fade'; - const CLASS_NAME_SHOW$5 = 'show'; - const EVENT_MOUSEDOWN = `mousedown.bs.${NAME$9}`; - const Default$8 = { - className: 'modal-backdrop', - clickCallback: null, - isAnimated: false, - isVisible: true, - // if false, we use the backdrop helper without adding any element to the dom - rootElement: 'body' // give the choice to place backdrop under different elements - }; - const DefaultType$8 = { - className: 'string', - clickCallback: '(function|null)', - isAnimated: 'boolean', - isVisible: 'boolean', - rootElement: '(element|string)' - }; - - /** - * Class definition - */ - - class Backdrop extends Config { - constructor(config) { - super(); - this._config = this._getConfig(config); - this._isAppended = false; - this._element = null; - } + Tooltip.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) + } - // Getters - static get Default() { - return Default$8; - } - static get DefaultType() { - return DefaultType$8; - } - static get NAME() { - return NAME$9; - } + Tooltip.prototype.enable = function () { + this.enabled = true + } - // Public - show(callback) { - if (!this._config.isVisible) { - execute(callback); - return; - } - this._append(); - const element = this._getElement(); - if (this._config.isAnimated) { - reflow(element); - } - element.classList.add(CLASS_NAME_SHOW$5); - this._emulateAnimation(() => { - execute(callback); - }); - } - hide(callback) { - if (!this._config.isVisible) { - execute(callback); - return; - } - this._getElement().classList.remove(CLASS_NAME_SHOW$5); - this._emulateAnimation(() => { - this.dispose(); - execute(callback); - }); - } - dispose() { - if (!this._isAppended) { - return; - } - EventHandler.off(this._element, EVENT_MOUSEDOWN); - this._element.remove(); - this._isAppended = false; - } + Tooltip.prototype.disable = function () { + this.enabled = false + } - // Private - _getElement() { - if (!this._element) { - const backdrop = document.createElement('div'); - backdrop.className = this._config.className; - if (this._config.isAnimated) { - backdrop.classList.add(CLASS_NAME_FADE$4); - } - this._element = backdrop; - } - return this._element; - } - _configAfterMerge(config) { - // use getElement() with the default "body" to get a fresh Element on each instantiation - config.rootElement = getElement(config.rootElement); - return config; - } - _append() { - if (this._isAppended) { - return; + Tooltip.prototype.toggleEnabled = function () { + this.enabled = !this.enabled + } + + Tooltip.prototype.toggle = function (e) { + var self = this + if (e) { + self = $(e.currentTarget).data('bs.' + this.type) + if (!self) { + self = new this.constructor(e.currentTarget, this.getDelegateOptions()) + $(e.currentTarget).data('bs.' + this.type, self) } - const element = this._getElement(); - this._config.rootElement.append(element); - EventHandler.on(element, EVENT_MOUSEDOWN, () => { - execute(this._config.clickCallback); - }); - this._isAppended = true; } - _emulateAnimation(callback) { - executeAfterTransition(callback, this._getElement(), this._config.isAnimated); + + if (e) { + self.inState.click = !self.inState.click + if (self.isInStateTrue()) self.enter(self) + else self.leave(self) + } else { + self.tip().hasClass('in') ? self.leave(self) : self.enter(self) } } - /** - * -------------------------------------------------------------------------- - * Bootstrap util/focustrap.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - - - /** - * Constants - */ - - const NAME$8 = 'focustrap'; - const DATA_KEY$5 = 'bs.focustrap'; - const EVENT_KEY$5 = `.${DATA_KEY$5}`; - const EVENT_FOCUSIN$2 = `focusin${EVENT_KEY$5}`; - const EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY$5}`; - const TAB_KEY = 'Tab'; - const TAB_NAV_FORWARD = 'forward'; - const TAB_NAV_BACKWARD = 'backward'; - const Default$7 = { - autofocus: true, - trapElement: null // The element to trap focus inside of - }; - const DefaultType$7 = { - autofocus: 'boolean', - trapElement: 'element' - }; - - /** - * Class definition - */ - - class FocusTrap extends Config { - constructor(config) { - super(); - this._config = this._getConfig(config); - this._isActive = false; - this._lastTabNavDirection = null; - } + Tooltip.prototype.destroy = function () { + var that = this + clearTimeout(this.timeout) + this.hide(function () { + that.$element.off('.' + that.type).removeData('bs.' + that.type) + if (that.$tip) { + that.$tip.detach() + } + that.$tip = null + that.$arrow = null + that.$viewport = null + that.$element = null + }) + } - // Getters - static get Default() { - return Default$7; - } - static get DefaultType() { - return DefaultType$7; - } - static get NAME() { - return NAME$8; - } - // Public - activate() { - if (this._isActive) { - return; - } - if (this._config.autofocus) { - this._config.trapElement.focus(); - } - EventHandler.off(document, EVENT_KEY$5); // guard against infinite focus loop - EventHandler.on(document, EVENT_FOCUSIN$2, event => this._handleFocusin(event)); - EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event)); - this._isActive = true; - } - deactivate() { - if (!this._isActive) { - return; - } - this._isActive = false; - EventHandler.off(document, EVENT_KEY$5); - } + // TOOLTIP PLUGIN DEFINITION + // ========================= - // Private - _handleFocusin(event) { - const { - trapElement - } = this._config; - if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) { - return; - } - const elements = SelectorEngine.focusableChildren(trapElement); - if (elements.length === 0) { - trapElement.focus(); - } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) { - elements[elements.length - 1].focus(); - } else { - elements[0].focus(); - } - } - _handleKeydown(event) { - if (event.key !== TAB_KEY) { - return; - } - this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD; - } + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tooltip') + var options = typeof option == 'object' && option + + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) + if (typeof option == 'string') data[option]() + }) } - /** - * -------------------------------------------------------------------------- - * Bootstrap util/scrollBar.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ + var old = $.fn.tooltip + $.fn.tooltip = Plugin + $.fn.tooltip.Constructor = Tooltip - /** - * Constants - */ - const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'; - const SELECTOR_STICKY_CONTENT = '.sticky-top'; - const PROPERTY_PADDING = 'padding-right'; - const PROPERTY_MARGIN = 'margin-right'; + // TOOLTIP NO CONFLICT + // =================== - /** - * Class definition - */ + $.fn.tooltip.noConflict = function () { + $.fn.tooltip = old + return this + } - class ScrollBarHelper { - constructor() { - this._element = document.body; - } +}(jQuery); - // Public - getWidth() { - // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes - const documentWidth = document.documentElement.clientWidth; - return Math.abs(window.innerWidth - documentWidth); - } - hide() { - const width = this.getWidth(); - this._disableOverFlow(); - // give padding to element to balance the hidden scrollbar width - this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width); - // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth - this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width); - this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width); - } - reset() { - this._resetElementAttributes(this._element, 'overflow'); - this._resetElementAttributes(this._element, PROPERTY_PADDING); - this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING); - this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN); - } - isOverflowing() { - return this.getWidth() > 0; - } +/* ======================================================================== + * Bootstrap: popover.js v3.3.7 + * http://getbootstrap.com/javascript/#popovers + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ - // Private - _disableOverFlow() { - this._saveInitialAttribute(this._element, 'overflow'); - this._element.style.overflow = 'hidden'; - } - _setElementAttributes(selector, styleProperty, callback) { - const scrollbarWidth = this.getWidth(); - const manipulationCallBack = element => { - if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) { - return; - } - this._saveInitialAttribute(element, styleProperty); - const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty); - element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`); - }; - this._applyManipulationCallback(selector, manipulationCallBack); - } - _saveInitialAttribute(element, styleProperty) { - const actualValue = element.style.getPropertyValue(styleProperty); - if (actualValue) { - Manipulator.setDataAttribute(element, styleProperty, actualValue); - } - } - _resetElementAttributes(selector, styleProperty) { - const manipulationCallBack = element => { - const value = Manipulator.getDataAttribute(element, styleProperty); - // We only want to remove the property if the value is `null`; the value can also be zero - if (value === null) { - element.style.removeProperty(styleProperty); - return; - } - Manipulator.removeDataAttribute(element, styleProperty); - element.style.setProperty(styleProperty, value); - }; - this._applyManipulationCallback(selector, manipulationCallBack); - } - _applyManipulationCallback(selector, callBack) { - if (isElement(selector)) { - callBack(selector); - return; - } - for (const sel of SelectorEngine.find(selector, this._element)) { - callBack(sel); - } - } + ++function ($) { + 'use strict'; + + // POPOVER PUBLIC CLASS DEFINITION + // =============================== + + var Popover = function (element, options) { + this.init('popover', element, options) } - /** - * -------------------------------------------------------------------------- - * Bootstrap modal.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - - - /** - * Constants - */ - - const NAME$7 = 'modal'; - const DATA_KEY$4 = 'bs.modal'; - const EVENT_KEY$4 = `.${DATA_KEY$4}`; - const DATA_API_KEY$2 = '.data-api'; - const ESCAPE_KEY$1 = 'Escape'; - const EVENT_HIDE$4 = `hide${EVENT_KEY$4}`; - const EVENT_HIDE_PREVENTED$1 = `hidePrevented${EVENT_KEY$4}`; - const EVENT_HIDDEN$4 = `hidden${EVENT_KEY$4}`; - const EVENT_SHOW$4 = `show${EVENT_KEY$4}`; - const EVENT_SHOWN$4 = `shown${EVENT_KEY$4}`; - const EVENT_RESIZE$1 = `resize${EVENT_KEY$4}`; - const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY$4}`; - const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY$4}`; - const EVENT_KEYDOWN_DISMISS$1 = `keydown.dismiss${EVENT_KEY$4}`; - const EVENT_CLICK_DATA_API$2 = `click${EVENT_KEY$4}${DATA_API_KEY$2}`; - const CLASS_NAME_OPEN = 'modal-open'; - const CLASS_NAME_FADE$3 = 'fade'; - const CLASS_NAME_SHOW$4 = 'show'; - const CLASS_NAME_STATIC = 'modal-static'; - const OPEN_SELECTOR$1 = '.modal.show'; - const SELECTOR_DIALOG = '.modal-dialog'; - const SELECTOR_MODAL_BODY = '.modal-body'; - const SELECTOR_DATA_TOGGLE$2 = '[data-bs-toggle="modal"]'; - const Default$6 = { - backdrop: true, - focus: true, - keyboard: true - }; - const DefaultType$6 = { - backdrop: '(boolean|string)', - focus: 'boolean', - keyboard: 'boolean' - }; - - /** - * Class definition - */ - - class Modal extends BaseComponent { - constructor(element, config) { - super(element, config); - this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element); - this._backdrop = this._initializeBackDrop(); - this._focustrap = this._initializeFocusTrap(); - this._isShown = false; - this._isTransitioning = false; - this._scrollBar = new ScrollBarHelper(); - this._addEventListeners(); - } + if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') - // Getters - static get Default() { - return Default$6; - } - static get DefaultType() { - return DefaultType$6; - } - static get NAME() { - return NAME$7; - } + Popover.VERSION = '3.3.7' - // Public - toggle(relatedTarget) { - return this._isShown ? this.hide() : this.show(relatedTarget); - } - show(relatedTarget) { - if (this._isShown || this._isTransitioning) { - return; - } - const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$4, { - relatedTarget - }); - if (showEvent.defaultPrevented) { - return; - } - this._isShown = true; - this._isTransitioning = true; - this._scrollBar.hide(); - document.body.classList.add(CLASS_NAME_OPEN); - this._adjustDialog(); - this._backdrop.show(() => this._showElement(relatedTarget)); - } - hide() { - if (!this._isShown || this._isTransitioning) { - return; - } - const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$4); - if (hideEvent.defaultPrevented) { - return; - } - this._isShown = false; - this._isTransitioning = true; - this._focustrap.deactivate(); - this._element.classList.remove(CLASS_NAME_SHOW$4); - this._queueCallback(() => this._hideModal(), this._element, this._isAnimated()); - } - dispose() { - EventHandler.off(window, EVENT_KEY$4); - EventHandler.off(this._dialog, EVENT_KEY$4); - this._backdrop.dispose(); - this._focustrap.deactivate(); - super.dispose(); - } - handleUpdate() { - this._adjustDialog(); - } + Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { + placement: 'right', + trigger: 'click', + content: '', + template: '' + }) - // Private - _initializeBackDrop() { - return new Backdrop({ - isVisible: Boolean(this._config.backdrop), - // 'static' option will be translated to true, and booleans will keep their value, - isAnimated: this._isAnimated() - }); - } - _initializeFocusTrap() { - return new FocusTrap({ - trapElement: this._element - }); - } - _showElement(relatedTarget) { - // try to append dynamic modal - if (!document.body.contains(this._element)) { - document.body.append(this._element); - } - this._element.style.display = 'block'; - this._element.removeAttribute('aria-hidden'); - this._element.setAttribute('aria-modal', true); - this._element.setAttribute('role', 'dialog'); - this._element.scrollTop = 0; - const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog); - if (modalBody) { - modalBody.scrollTop = 0; - } - reflow(this._element); - this._element.classList.add(CLASS_NAME_SHOW$4); - const transitionComplete = () => { - if (this._config.focus) { - this._focustrap.activate(); - } - this._isTransitioning = false; - EventHandler.trigger(this._element, EVENT_SHOWN$4, { - relatedTarget - }); - }; - this._queueCallback(transitionComplete, this._dialog, this._isAnimated()); - } - _addEventListeners() { - EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS$1, event => { - if (event.key !== ESCAPE_KEY$1) { - return; - } - if (this._config.keyboard) { - this.hide(); - return; - } - this._triggerBackdropTransition(); - }); - EventHandler.on(window, EVENT_RESIZE$1, () => { - if (this._isShown && !this._isTransitioning) { - this._adjustDialog(); - } - }); - EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => { - // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks - EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => { - if (this._element !== event.target || this._element !== event2.target) { - return; - } - if (this._config.backdrop === 'static') { - this._triggerBackdropTransition(); - return; - } - if (this._config.backdrop) { - this.hide(); - } - }); - }); - } - _hideModal() { - this._element.style.display = 'none'; - this._element.setAttribute('aria-hidden', true); - this._element.removeAttribute('aria-modal'); - this._element.removeAttribute('role'); - this._isTransitioning = false; - this._backdrop.hide(() => { - document.body.classList.remove(CLASS_NAME_OPEN); - this._resetAdjustments(); - this._scrollBar.reset(); - EventHandler.trigger(this._element, EVENT_HIDDEN$4); - }); - } - _isAnimated() { - return this._element.classList.contains(CLASS_NAME_FADE$3); - } - _triggerBackdropTransition() { - const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED$1); - if (hideEvent.defaultPrevented) { - return; - } - const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight; - const initialOverflowY = this._element.style.overflowY; - // return if the following background transition hasn't yet completed - if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) { - return; - } - if (!isModalOverflowing) { - this._element.style.overflowY = 'hidden'; - } - this._element.classList.add(CLASS_NAME_STATIC); - this._queueCallback(() => { - this._element.classList.remove(CLASS_NAME_STATIC); - this._queueCallback(() => { - this._element.style.overflowY = initialOverflowY; - }, this._dialog); - }, this._dialog); - this._element.focus(); - } - /** - * The following methods are used to handle overflowing modals - */ - - _adjustDialog() { - const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight; - const scrollbarWidth = this._scrollBar.getWidth(); - const isBodyOverflowing = scrollbarWidth > 0; - if (isBodyOverflowing && !isModalOverflowing) { - const property = isRTL() ? 'paddingLeft' : 'paddingRight'; - this._element.style[property] = `${scrollbarWidth}px`; - } - if (!isBodyOverflowing && isModalOverflowing) { - const property = isRTL() ? 'paddingRight' : 'paddingLeft'; - this._element.style[property] = `${scrollbarWidth}px`; - } - } - _resetAdjustments() { - this._element.style.paddingLeft = ''; - this._element.style.paddingRight = ''; - } + // NOTE: POPOVER EXTENDS tooltip.js + // ================================ - // Static - static jQueryInterface(config, relatedTarget) { - return this.each(function () { - const data = Modal.getOrCreateInstance(this, config); - if (typeof config !== 'string') { - return; - } - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`); - } - data[config](relatedTarget); - }); - } + Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) + + Popover.prototype.constructor = Popover + + Popover.prototype.getDefaults = function () { + return Popover.DEFAULTS } - /** - * Data API implementation - */ + Popover.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + var content = this.getContent() - EventHandler.on(document, EVENT_CLICK_DATA_API$2, SELECTOR_DATA_TOGGLE$2, function (event) { - const target = SelectorEngine.getElementFromSelector(this); - if (['A', 'AREA'].includes(this.tagName)) { - event.preventDefault(); - } - EventHandler.one(target, EVENT_SHOW$4, showEvent => { - if (showEvent.defaultPrevented) { - // only register focus restorer if modal will actually get shown - return; - } - EventHandler.one(target, EVENT_HIDDEN$4, () => { - if (isVisible(this)) { - this.focus(); - } - }); - }); + $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) + $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events + this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text' + ](content) - // avoid conflict when clicking modal toggler while another one is open - const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR$1); - if (alreadyOpen) { - Modal.getInstance(alreadyOpen).hide(); - } - const data = Modal.getOrCreateInstance(target); - data.toggle(this); - }); - enableDismissTrigger(Modal); - - /** - * jQuery - */ - - defineJQueryPlugin(Modal); - - /** - * -------------------------------------------------------------------------- - * Bootstrap offcanvas.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - - - /** - * Constants - */ - - const NAME$6 = 'offcanvas'; - const DATA_KEY$3 = 'bs.offcanvas'; - const EVENT_KEY$3 = `.${DATA_KEY$3}`; - const DATA_API_KEY$1 = '.data-api'; - const EVENT_LOAD_DATA_API$2 = `load${EVENT_KEY$3}${DATA_API_KEY$1}`; - const ESCAPE_KEY = 'Escape'; - const CLASS_NAME_SHOW$3 = 'show'; - const CLASS_NAME_SHOWING$1 = 'showing'; - const CLASS_NAME_HIDING = 'hiding'; - const CLASS_NAME_BACKDROP = 'offcanvas-backdrop'; - const OPEN_SELECTOR = '.offcanvas.show'; - const EVENT_SHOW$3 = `show${EVENT_KEY$3}`; - const EVENT_SHOWN$3 = `shown${EVENT_KEY$3}`; - const EVENT_HIDE$3 = `hide${EVENT_KEY$3}`; - const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY$3}`; - const EVENT_HIDDEN$3 = `hidden${EVENT_KEY$3}`; - const EVENT_RESIZE = `resize${EVENT_KEY$3}`; - const EVENT_CLICK_DATA_API$1 = `click${EVENT_KEY$3}${DATA_API_KEY$1}`; - const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY$3}`; - const SELECTOR_DATA_TOGGLE$1 = '[data-bs-toggle="offcanvas"]'; - const Default$5 = { - backdrop: true, - keyboard: true, - scroll: false - }; - const DefaultType$5 = { - backdrop: '(boolean|string)', - keyboard: 'boolean', - scroll: 'boolean' - }; - - /** - * Class definition - */ - - class Offcanvas extends BaseComponent { - constructor(element, config) { - super(element, config); - this._isShown = false; - this._backdrop = this._initializeBackDrop(); - this._focustrap = this._initializeFocusTrap(); - this._addEventListeners(); - } + $tip.removeClass('fade top bottom left right in') - // Getters - static get Default() { - return Default$5; - } - static get DefaultType() { - return DefaultType$5; - } - static get NAME() { - return NAME$6; - } + // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do + // this manually by checking the contents. + if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() + } - // Public - toggle(relatedTarget) { - return this._isShown ? this.hide() : this.show(relatedTarget); - } - show(relatedTarget) { - if (this._isShown) { - return; - } - const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$3, { - relatedTarget - }); - if (showEvent.defaultPrevented) { - return; - } - this._isShown = true; - this._backdrop.show(); - if (!this._config.scroll) { - new ScrollBarHelper().hide(); - } - this._element.setAttribute('aria-modal', true); - this._element.setAttribute('role', 'dialog'); - this._element.classList.add(CLASS_NAME_SHOWING$1); - const completeCallBack = () => { - if (!this._config.scroll || this._config.backdrop) { - this._focustrap.activate(); - } - this._element.classList.add(CLASS_NAME_SHOW$3); - this._element.classList.remove(CLASS_NAME_SHOWING$1); - EventHandler.trigger(this._element, EVENT_SHOWN$3, { - relatedTarget - }); - }; - this._queueCallback(completeCallBack, this._element, true); - } - hide() { - if (!this._isShown) { - return; - } - const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$3); - if (hideEvent.defaultPrevented) { - return; - } - this._focustrap.deactivate(); - this._element.blur(); - this._isShown = false; - this._element.classList.add(CLASS_NAME_HIDING); - this._backdrop.hide(); - const completeCallback = () => { - this._element.classList.remove(CLASS_NAME_SHOW$3, CLASS_NAME_HIDING); - this._element.removeAttribute('aria-modal'); - this._element.removeAttribute('role'); - if (!this._config.scroll) { - new ScrollBarHelper().reset(); - } - EventHandler.trigger(this._element, EVENT_HIDDEN$3); - }; - this._queueCallback(completeCallback, this._element, true); - } - dispose() { - this._backdrop.dispose(); - this._focustrap.deactivate(); - super.dispose(); - } + Popover.prototype.hasContent = function () { + return this.getTitle() || this.getContent() + } - // Private - _initializeBackDrop() { - const clickCallback = () => { - if (this._config.backdrop === 'static') { - EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED); - return; - } - this.hide(); - }; - - // 'static' option will be translated to true, and booleans will keep their value - const isVisible = Boolean(this._config.backdrop); - return new Backdrop({ - className: CLASS_NAME_BACKDROP, - isVisible, - isAnimated: true, - rootElement: this._element.parentNode, - clickCallback: isVisible ? clickCallback : null - }); - } - _initializeFocusTrap() { - return new FocusTrap({ - trapElement: this._element - }); - } - _addEventListeners() { - EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => { - if (event.key !== ESCAPE_KEY) { - return; - } - if (this._config.keyboard) { - this.hide(); - return; - } - EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED); - }); - } + Popover.prototype.getContent = function () { + var $e = this.$element + var o = this.options - // Static - static jQueryInterface(config) { - return this.each(function () { - const data = Offcanvas.getOrCreateInstance(this, config); - if (typeof config !== 'string') { - return; - } - if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { - throw new TypeError(`No method named "${config}"`); - } - data[config](this); - }); - } + return $e.attr('data-content') + || (typeof o.content == 'function' ? + o.content.call($e[0]) : + o.content) } - /** - * Data API implementation - */ + Popover.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.arrow')) + } - EventHandler.on(document, EVENT_CLICK_DATA_API$1, SELECTOR_DATA_TOGGLE$1, function (event) { - const target = SelectorEngine.getElementFromSelector(this); - if (['A', 'AREA'].includes(this.tagName)) { - event.preventDefault(); - } - if (isDisabled(this)) { - return; - } - EventHandler.one(target, EVENT_HIDDEN$3, () => { - // focus on trigger when it is closed - if (isVisible(this)) { - this.focus(); - } - }); - // avoid conflict when clicking a toggler of an offcanvas, while another is open - const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR); - if (alreadyOpen && alreadyOpen !== target) { - Offcanvas.getInstance(alreadyOpen).hide(); - } - const data = Offcanvas.getOrCreateInstance(target); - data.toggle(this); - }); - EventHandler.on(window, EVENT_LOAD_DATA_API$2, () => { - for (const selector of SelectorEngine.find(OPEN_SELECTOR)) { - Offcanvas.getOrCreateInstance(selector).show(); - } - }); - EventHandler.on(window, EVENT_RESIZE, () => { - for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) { - if (getComputedStyle(element).position !== 'fixed') { - Offcanvas.getOrCreateInstance(element).hide(); - } + // POPOVER PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.popover') + var options = typeof option == 'object' && option + + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.popover', (data = new Popover(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.popover + + $.fn.popover = Plugin + $.fn.popover.Constructor = Popover + + + // POPOVER NO CONFLICT + // =================== + + $.fn.popover.noConflict = function () { + $.fn.popover = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: scrollspy.js v3.3.7 + * http://getbootstrap.com/javascript/#scrollspy + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // SCROLLSPY CLASS DEFINITION + // ========================== + + function ScrollSpy(element, options) { + this.$body = $(document.body) + this.$scrollElement = $(element).is(document.body) ? $(window) : $(element) + this.options = $.extend({}, ScrollSpy.DEFAULTS, options) + this.selector = (this.options.target || '') + ' .nav li > a' + this.offsets = [] + this.targets = [] + this.activeTarget = null + this.scrollHeight = 0 + + this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this)) + this.refresh() + this.process() + } + + ScrollSpy.VERSION = '3.3.7' + + ScrollSpy.DEFAULTS = { + offset: 10 + } + + ScrollSpy.prototype.getScrollHeight = function () { + return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight) + } + + ScrollSpy.prototype.refresh = function () { + var that = this + var offsetMethod = 'offset' + var offsetBase = 0 + + this.offsets = [] + this.targets = [] + this.scrollHeight = this.getScrollHeight() + + if (!$.isWindow(this.$scrollElement[0])) { + offsetMethod = 'position' + offsetBase = this.$scrollElement.scrollTop() + } + + this.$body + .find(this.selector) + .map(function () { + var $el = $(this) + var href = $el.data('target') || $el.attr('href') + var $href = /^#./.test(href) && $(href) + + return ($href + && $href.length + && $href.is(':visible') + && [[$href[offsetMethod]().top + offsetBase, href]]) || null + }) + .sort(function (a, b) { return a[0] - b[0] }) + .each(function () { + that.offsets.push(this[0]) + that.targets.push(this[1]) + }) + } + + ScrollSpy.prototype.process = function () { + var scrollTop = this.$scrollElement.scrollTop() + this.options.offset + var scrollHeight = this.getScrollHeight() + var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height() + var offsets = this.offsets + var targets = this.targets + var activeTarget = this.activeTarget + var i + + if (this.scrollHeight != scrollHeight) { + this.refresh() } - }); - enableDismissTrigger(Offcanvas); - - /** - * jQuery - */ - - defineJQueryPlugin(Offcanvas); - - /** - * -------------------------------------------------------------------------- - * Bootstrap util/sanitizer.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - - // js-docs-start allow-list - const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i; - const DefaultAllowlist = { - // Global attributes allowed on any supplied element below. - '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN], - a: ['target', 'href', 'title', 'rel'], - area: [], - b: [], - br: [], - col: [], - code: [], - dd: [], - div: [], - dl: [], - dt: [], - em: [], - hr: [], - h1: [], - h2: [], - h3: [], - h4: [], - h5: [], - h6: [], - i: [], - img: ['src', 'srcset', 'alt', 'title', 'width', 'height'], - li: [], - ol: [], - p: [], - pre: [], - s: [], - small: [], - span: [], - sub: [], - sup: [], - strong: [], - u: [], - ul: [] - }; - // js-docs-end allow-list - - const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']); - - /** - * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation - * contexts. - * - * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38 - */ - // eslint-disable-next-line unicorn/better-regex - const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i; - const allowedAttribute = (attribute, allowedAttributeList) => { - const attributeName = attribute.nodeName.toLowerCase(); - if (allowedAttributeList.includes(attributeName)) { - if (uriAttributes.has(attributeName)) { - return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue)); - } - return true; + + if (scrollTop >= maxScroll) { + return activeTarget != (i = targets[targets.length - 1]) && this.activate(i) } - // Check if a regular expression validates the attribute. - return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName)); - }; - function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) { - if (!unsafeHtml.length) { - return unsafeHtml; + if (activeTarget && scrollTop < offsets[0]) { + this.activeTarget = null + return this.clear() } - if (sanitizeFunction && typeof sanitizeFunction === 'function') { - return sanitizeFunction(unsafeHtml); + + for (i = offsets.length; i--;) { + activeTarget != targets[i] + && scrollTop >= offsets[i] + && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) + && this.activate(targets[i]) } - const domParser = new window.DOMParser(); - const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html'); - const elements = [].concat(...createdDocument.body.querySelectorAll('*')); - for (const element of elements) { - const elementName = element.nodeName.toLowerCase(); - if (!Object.keys(allowList).includes(elementName)) { - element.remove(); - continue; - } - const attributeList = [].concat(...element.attributes); - const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []); - for (const attribute of attributeList) { - if (!allowedAttribute(attribute, allowedAttributes)) { - element.removeAttribute(attribute.nodeName); - } - } + } + + ScrollSpy.prototype.activate = function (target) { + this.activeTarget = target + + this.clear() + + var selector = this.selector + + '[data-target="' + target + '"],' + + this.selector + '[href="' + target + '"]' + + var active = $(selector) + .parents('li') + .addClass('active') + + if (active.parent('.dropdown-menu').length) { + active = active + .closest('li.dropdown') + .addClass('active') } - return createdDocument.body.innerHTML; + + active.trigger('activate.bs.scrollspy') + } + + ScrollSpy.prototype.clear = function () { + $(this.selector) + .parentsUntil(this.options.target, '.active') + .removeClass('active') + } + + + // SCROLLSPY PLUGIN DEFINITION + // =========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.scrollspy') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.scrollspy + + $.fn.scrollspy = Plugin + $.fn.scrollspy.Constructor = ScrollSpy + + + // SCROLLSPY NO CONFLICT + // ===================== + + $.fn.scrollspy.noConflict = function () { + $.fn.scrollspy = old + return this } - /** - * -------------------------------------------------------------------------- - * Bootstrap util/template-factory.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ + // SCROLLSPY DATA-API + // ================== - /** - * Constants - */ + $(window).on('load.bs.scrollspy.data-api', function () { + $('[data-spy="scroll"]').each(function () { + var $spy = $(this) + Plugin.call($spy, $spy.data()) + }) + }) - const NAME$5 = 'TemplateFactory'; - const Default$4 = { - allowList: DefaultAllowlist, - content: {}, - // { selector : text , selector2 : text2 , } - extraClass: '', - html: false, - sanitize: true, - sanitizeFn: null, - template: '
' - }; - const DefaultType$4 = { - allowList: 'object', - content: 'object', - extraClass: '(string|function)', - html: 'boolean', - sanitize: 'boolean', - sanitizeFn: '(null|function)', - template: 'string' - }; - const DefaultContentType = { - entry: '(string|element|function|null)', - selector: '(string|element)' - }; - - /** - * Class definition - */ - - class TemplateFactory extends Config { - constructor(config) { - super(); - this._config = this._getConfig(config); - } +}(jQuery); - // Getters - static get Default() { - return Default$4; - } - static get DefaultType() { - return DefaultType$4; - } - static get NAME() { - return NAME$5; - } +/* ======================================================================== + * Bootstrap: tab.js v3.3.7 + * http://getbootstrap.com/javascript/#tabs + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ - // Public - getContent() { - return Object.values(this._config.content).map(config => this._resolvePossibleFunction(config)).filter(Boolean); - } - hasContent() { - return this.getContent().length > 0; - } - changeContent(content) { - this._checkContent(content); - this._config.content = { - ...this._config.content, - ...content - }; - return this; - } - toHtml() { - const templateWrapper = document.createElement('div'); - templateWrapper.innerHTML = this._maybeSanitize(this._config.template); - for (const [selector, text] of Object.entries(this._config.content)) { - this._setContent(templateWrapper, text, selector); - } - const template = templateWrapper.children[0]; - const extraClass = this._resolvePossibleFunction(this._config.extraClass); - if (extraClass) { - template.classList.add(...extraClass.split(' ')); - } - return template; - } - // Private - _typeCheckConfig(config) { - super._typeCheckConfig(config); - this._checkContent(config.content); - } - _checkContent(arg) { - for (const [selector, content] of Object.entries(arg)) { - super._typeCheckConfig({ - selector, - entry: content - }, DefaultContentType); - } - } - _setContent(template, content, selector) { - const templateElement = SelectorEngine.findOne(selector, template); - if (!templateElement) { - return; - } - content = this._resolvePossibleFunction(content); - if (!content) { - templateElement.remove(); - return; - } - if (isElement(content)) { - this._putElementInTemplate(getElement(content), templateElement); - return; - } - if (this._config.html) { - templateElement.innerHTML = this._maybeSanitize(content); - return; - } - templateElement.textContent = content; - } - _maybeSanitize(arg) { - return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg; - } - _resolvePossibleFunction(arg) { - return execute(arg, [this]); - } - _putElementInTemplate(element, templateElement) { - if (this._config.html) { - templateElement.innerHTML = ''; - templateElement.append(element); - return; - } - templateElement.textContent = element.textContent; - } ++function ($) { + 'use strict'; + + // TAB CLASS DEFINITION + // ==================== + + var Tab = function (element) { + // jscs:disable requireDollarBeforejQueryAssignment + this.element = $(element) + // jscs:enable requireDollarBeforejQueryAssignment } - /** - * -------------------------------------------------------------------------- - * Bootstrap tooltip.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - - - /** - * Constants - */ - - const NAME$4 = 'tooltip'; - const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']); - const CLASS_NAME_FADE$2 = 'fade'; - const CLASS_NAME_MODAL = 'modal'; - const CLASS_NAME_SHOW$2 = 'show'; - const SELECTOR_TOOLTIP_INNER = '.tooltip-inner'; - const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`; - const EVENT_MODAL_HIDE = 'hide.bs.modal'; - const TRIGGER_HOVER = 'hover'; - const TRIGGER_FOCUS = 'focus'; - const TRIGGER_CLICK = 'click'; - const TRIGGER_MANUAL = 'manual'; - const EVENT_HIDE$2 = 'hide'; - const EVENT_HIDDEN$2 = 'hidden'; - const EVENT_SHOW$2 = 'show'; - const EVENT_SHOWN$2 = 'shown'; - const EVENT_INSERTED = 'inserted'; - const EVENT_CLICK$1 = 'click'; - const EVENT_FOCUSIN$1 = 'focusin'; - const EVENT_FOCUSOUT$1 = 'focusout'; - const EVENT_MOUSEENTER = 'mouseenter'; - const EVENT_MOUSELEAVE = 'mouseleave'; - const AttachmentMap = { - AUTO: 'auto', - TOP: 'top', - RIGHT: isRTL() ? 'left' : 'right', - BOTTOM: 'bottom', - LEFT: isRTL() ? 'right' : 'left' - }; - const Default$3 = { - allowList: DefaultAllowlist, - animation: true, - boundary: 'clippingParents', - container: false, - customClass: '', - delay: 0, - fallbackPlacements: ['top', 'right', 'bottom', 'left'], - html: false, - offset: [0, 6], - placement: 'top', - popperConfig: null, - sanitize: true, - sanitizeFn: null, - selector: false, - template: '', - title: '', - trigger: 'hover focus' - }; - const DefaultType$3 = { - allowList: 'object', - animation: 'boolean', - boundary: '(string|element)', - container: '(string|element|boolean)', - customClass: '(string|function)', - delay: '(number|object)', - fallbackPlacements: 'array', - html: 'boolean', - offset: '(array|string|function)', - placement: '(string|function)', - popperConfig: '(null|object|function)', - sanitize: 'boolean', - sanitizeFn: '(null|function)', - selector: '(string|boolean)', - template: 'string', - title: '(string|element|function)', - trigger: 'string' - }; - - /** - * Class definition - */ - - class Tooltip extends BaseComponent { - constructor(element, config) { - if (typeof Popper__namespace === 'undefined') { - throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org)'); - } - super(element, config); - - // Private - this._isEnabled = true; - this._timeout = 0; - this._isHovered = null; - this._activeTrigger = {}; - this._popper = null; - this._templateFactory = null; - this._newContent = null; - - // Protected - this.tip = null; - this._setListeners(); - if (!this._config.selector) { - this._fixTitle(); - } - } + Tab.VERSION = '3.3.7' - // Getters - static get Default() { - return Default$3; - } - static get DefaultType() { - return DefaultType$3; - } - static get NAME() { - return NAME$4; - } + Tab.TRANSITION_DURATION = 150 - // Public - enable() { - this._isEnabled = true; - } - disable() { - this._isEnabled = false; - } - toggleEnabled() { - this._isEnabled = !this._isEnabled; - } - toggle() { - if (!this._isEnabled) { - return; - } - this._activeTrigger.click = !this._activeTrigger.click; - if (this._isShown()) { - this._leave(); - return; - } - this._enter(); - } - dispose() { - clearTimeout(this._timeout); - EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler); - if (this._element.getAttribute('data-bs-original-title')) { - this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title')); - } - this._disposePopper(); - super.dispose(); - } - show() { - if (this._element.style.display === 'none') { - throw new Error('Please use show on visible elements'); - } - if (!(this._isWithContent() && this._isEnabled)) { - return; - } - const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW$2)); - const shadowRoot = findShadowRoot(this._element); - const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element); - if (showEvent.defaultPrevented || !isInTheDom) { - return; - } + Tab.prototype.show = function () { + var $this = this.element + var $ul = $this.closest('ul:not(.dropdown-menu)') + var selector = $this.data('target') - // TODO: v6 remove this or make it optional - this._disposePopper(); - const tip = this._getTipElement(); - this._element.setAttribute('aria-describedby', tip.getAttribute('id')); - const { - container - } = this._config; - if (!this._element.ownerDocument.documentElement.contains(this.tip)) { - container.append(tip); - EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED)); - } - this._popper = this._createPopper(tip); - tip.classList.add(CLASS_NAME_SHOW$2); - - // If this is a touch-enabled device we add extra - // empty mouseover listeners to the body's immediate children; - // only needed because of broken event delegation on iOS - // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html - if ('ontouchstart' in document.documentElement) { - for (const element of [].concat(...document.body.children)) { - EventHandler.on(element, 'mouseover', noop); - } - } - const complete = () => { - EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN$2)); - if (this._isHovered === false) { - this._leave(); - } - this._isHovered = false; - }; - this._queueCallback(complete, this.tip, this._isAnimated()); - } - hide() { - if (!this._isShown()) { - return; - } - const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE$2)); - if (hideEvent.defaultPrevented) { - return; - } - const tip = this._getTipElement(); - tip.classList.remove(CLASS_NAME_SHOW$2); - - // If this is a touch-enabled device we remove the extra - // empty mouseover listeners we added for iOS support - if ('ontouchstart' in document.documentElement) { - for (const element of [].concat(...document.body.children)) { - EventHandler.off(element, 'mouseover', noop); - } - } - this._activeTrigger[TRIGGER_CLICK] = false; - this._activeTrigger[TRIGGER_FOCUS] = false; - this._activeTrigger[TRIGGER_HOVER] = false; - this._isHovered = null; // it is a trick to support manual triggering - - const complete = () => { - if (this._isWithActiveTrigger()) { - return; - } - if (!this._isHovered) { - this._disposePopper(); - } - this._element.removeAttribute('aria-describedby'); - EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN$2)); - }; - this._queueCallback(complete, this.tip, this._isAnimated()); - } - update() { - if (this._popper) { - this._popper.update(); - } + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 } - // Protected - _isWithContent() { - return Boolean(this._getTitle()); - } - _getTipElement() { - if (!this.tip) { - this.tip = this._createTipElement(this._newContent || this._getContentForTemplate()); - } - return this.tip; - } - _createTipElement(content) { - const tip = this._getTemplateFactory(content).toHtml(); + if ($this.parent('li').hasClass('active')) return - // TODO: remove this check in v6 - if (!tip) { - return null; - } - tip.classList.remove(CLASS_NAME_FADE$2, CLASS_NAME_SHOW$2); - // TODO: v6 the following can be achieved with CSS only - tip.classList.add(`bs-${this.constructor.NAME}-auto`); - const tipId = getUID(this.constructor.NAME).toString(); - tip.setAttribute('id', tipId); - if (this._isAnimated()) { - tip.classList.add(CLASS_NAME_FADE$2); - } - return tip; - } - setContent(content) { - this._newContent = content; - if (this._isShown()) { - this._disposePopper(); - this.show(); - } - } - _getTemplateFactory(content) { - if (this._templateFactory) { - this._templateFactory.changeContent(content); + var $previous = $ul.find('.active:last a') + var hideEvent = $.Event('hide.bs.tab', { + relatedTarget: $this[0] + }) + var showEvent = $.Event('show.bs.tab', { + relatedTarget: $previous[0] + }) + + $previous.trigger(hideEvent) + $this.trigger(showEvent) + + if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return + + var $target = $(selector) + + this.activate($this.closest('li'), $ul) + this.activate($target, $target.parent(), function () { + $previous.trigger({ + type: 'hidden.bs.tab', + relatedTarget: $this[0] + }) + $this.trigger({ + type: 'shown.bs.tab', + relatedTarget: $previous[0] + }) + }) + } + + Tab.prototype.activate = function (element, container, callback) { + var $active = container.find('> .active') + var transition = callback + && $.support.transition + && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length) + + function next() { + $active + .removeClass('active') + .find('> .dropdown-menu > .active') + .removeClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', false) + + element + .addClass('active') + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + + if (transition) { + element[0].offsetWidth // reflow for transition + element.addClass('in') } else { - this._templateFactory = new TemplateFactory({ - ...this._config, - // the `content` var has to be after `this._config` - // to override config.content in case of popover - content, - extraClass: this._resolvePossibleFunction(this._config.customClass) - }); + element.removeClass('fade') } - return this._templateFactory; - } - _getContentForTemplate() { - return { - [SELECTOR_TOOLTIP_INNER]: this._getTitle() - }; - } - _getTitle() { - return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title'); - } - // Private - _initializeOnDelegatedTarget(event) { - return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig()); - } - _isAnimated() { - return this._config.animation || this.tip && this.tip.classList.contains(CLASS_NAME_FADE$2); - } - _isShown() { - return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW$2); - } - _createPopper(tip) { - const placement = execute(this._config.placement, [this, tip, this._element]); - const attachment = AttachmentMap[placement.toUpperCase()]; - return Popper__namespace.createPopper(this._element, tip, this._getPopperConfig(attachment)); - } - _getOffset() { - const { - offset - } = this._config; - if (typeof offset === 'string') { - return offset.split(',').map(value => Number.parseInt(value, 10)); - } - if (typeof offset === 'function') { - return popperData => offset(popperData, this._element); - } - return offset; - } - _resolvePossibleFunction(arg) { - return execute(arg, [this._element]); - } - _getPopperConfig(attachment) { - const defaultBsPopperConfig = { - placement: attachment, - modifiers: [{ - name: 'flip', - options: { - fallbackPlacements: this._config.fallbackPlacements - } - }, { - name: 'offset', - options: { - offset: this._getOffset() - } - }, { - name: 'preventOverflow', - options: { - boundary: this._config.boundary - } - }, { - name: 'arrow', - options: { - element: `.${this.constructor.NAME}-arrow` - } - }, { - name: 'preSetPlacement', - enabled: true, - phase: 'beforeMain', - fn: data => { - // Pre-set Popper's placement attribute in order to read the arrow sizes properly. - // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement - this._getTipElement().setAttribute('data-popper-placement', data.state.placement); - } - }] - }; - return { - ...defaultBsPopperConfig, - ...execute(this._config.popperConfig, [defaultBsPopperConfig]) - }; - } - _setListeners() { - const triggers = this._config.trigger.split(' '); - for (const trigger of triggers) { - if (trigger === 'click') { - EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK$1), this._config.selector, event => { - const context = this._initializeOnDelegatedTarget(event); - context.toggle(); - }); - } else if (trigger !== TRIGGER_MANUAL) { - const eventIn = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSEENTER) : this.constructor.eventName(EVENT_FOCUSIN$1); - const eventOut = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSELEAVE) : this.constructor.eventName(EVENT_FOCUSOUT$1); - EventHandler.on(this._element, eventIn, this._config.selector, event => { - const context = this._initializeOnDelegatedTarget(event); - context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true; - context._enter(); - }); - EventHandler.on(this._element, eventOut, this._config.selector, event => { - const context = this._initializeOnDelegatedTarget(event); - context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = context._element.contains(event.relatedTarget); - context._leave(); - }); - } - } - this._hideModalHandler = () => { - if (this._element) { - this.hide(); - } - }; - EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler); - } - _fixTitle() { - const title = this._element.getAttribute('title'); - if (!title) { - return; - } - if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) { - this._element.setAttribute('aria-label', title); - } - this._element.setAttribute('data-bs-original-title', title); // DO NOT USE IT. Is only for backwards compatibility - this._element.removeAttribute('title'); - } - _enter() { - if (this._isShown() || this._isHovered) { - this._isHovered = true; - return; - } - this._isHovered = true; - this._setTimeout(() => { - if (this._isHovered) { - this.show(); - } - }, this._config.delay.show); - } - _leave() { - if (this._isWithActiveTrigger()) { - return; - } - this._isHovered = false; - this._setTimeout(() => { - if (!this._isHovered) { - this.hide(); - } - }, this._config.delay.hide); - } - _setTimeout(handler, timeout) { - clearTimeout(this._timeout); - this._timeout = setTimeout(handler, timeout); - } - _isWithActiveTrigger() { - return Object.values(this._activeTrigger).includes(true); - } - _getConfig(config) { - const dataAttributes = Manipulator.getDataAttributes(this._element); - for (const dataAttribute of Object.keys(dataAttributes)) { - if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) { - delete dataAttributes[dataAttribute]; - } + if (element.parent('.dropdown-menu').length) { + element + .closest('li.dropdown') + .addClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) } - config = { - ...dataAttributes, - ...(typeof config === 'object' && config ? config : {}) - }; - config = this._mergeConfigObj(config); - config = this._configAfterMerge(config); - this._typeCheckConfig(config); - return config; - } - _configAfterMerge(config) { - config.container = config.container === false ? document.body : getElement(config.container); - if (typeof config.delay === 'number') { - config.delay = { - show: config.delay, - hide: config.delay - }; - } - if (typeof config.title === 'number') { - config.title = config.title.toString(); - } - if (typeof config.content === 'number') { - config.content = config.content.toString(); - } - return config; - } - _getDelegateConfig() { - const config = {}; - for (const [key, value] of Object.entries(this._config)) { - if (this.constructor.Default[key] !== value) { - config[key] = value; - } - } - config.selector = false; - config.trigger = 'manual'; - // In the future can be replaced with: - // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]]) - // `Object.fromEntries(keysWithDifferentValues)` - return config; - } - _disposePopper() { - if (this._popper) { - this._popper.destroy(); - this._popper = null; - } - if (this.tip) { - this.tip.remove(); - this.tip = null; - } + callback && callback() } - // Static - static jQueryInterface(config) { - return this.each(function () { - const data = Tooltip.getOrCreateInstance(this, config); - if (typeof config !== 'string') { - return; - } - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`); - } - data[config](); - }); - } + $active.length && transition ? + $active + .one('bsTransitionEnd', next) + .emulateTransitionEnd(Tab.TRANSITION_DURATION) : + next() + + $active.removeClass('in') } - /** - * jQuery - */ - defineJQueryPlugin(Tooltip); + // TAB PLUGIN DEFINITION + // ===================== - /** - * -------------------------------------------------------------------------- - * Bootstrap popover.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tab') + if (!data) $this.data('bs.tab', (data = new Tab(this))) + if (typeof option == 'string') data[option]() + }) + } - /** - * Constants - */ + var old = $.fn.tab - const NAME$3 = 'popover'; - const SELECTOR_TITLE = '.popover-header'; - const SELECTOR_CONTENT = '.popover-body'; - const Default$2 = { - ...Tooltip.Default, - content: '', - offset: [0, 8], - placement: 'right', - template: '', - trigger: 'click' - }; - const DefaultType$2 = { - ...Tooltip.DefaultType, - content: '(null|string|element|function)' - }; - - /** - * Class definition - */ - - class Popover extends Tooltip { - // Getters - static get Default() { - return Default$2; - } - static get DefaultType() { - return DefaultType$2; - } - static get NAME() { - return NAME$3; - } + $.fn.tab = Plugin + $.fn.tab.Constructor = Tab - // Overrides - _isWithContent() { - return this._getTitle() || this._getContent(); - } - // Private - _getContentForTemplate() { - return { - [SELECTOR_TITLE]: this._getTitle(), - [SELECTOR_CONTENT]: this._getContent() - }; - } - _getContent() { - return this._resolvePossibleFunction(this._config.content); - } + // TAB NO CONFLICT + // =============== - // Static - static jQueryInterface(config) { - return this.each(function () { - const data = Popover.getOrCreateInstance(this, config); - if (typeof config !== 'string') { - return; - } - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`); - } - data[config](); - }); - } + $.fn.tab.noConflict = function () { + $.fn.tab = old + return this } - /** - * jQuery - */ - - defineJQueryPlugin(Popover); - - /** - * -------------------------------------------------------------------------- - * Bootstrap scrollspy.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - * -------------------------------------------------------------------------- - */ - - - /** - * Constants - */ - - const NAME$2 = 'scrollspy'; - const DATA_KEY$2 = 'bs.scrollspy'; - const EVENT_KEY$2 = `.${DATA_KEY$2}`; - const DATA_API_KEY = '.data-api'; - const EVENT_ACTIVATE = `activate${EVENT_KEY$2}`; - const EVENT_CLICK = `click${EVENT_KEY$2}`; - const EVENT_LOAD_DATA_API$1 = `load${EVENT_KEY$2}${DATA_API_KEY}`; - const CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'; - const CLASS_NAME_ACTIVE$1 = 'active'; - const SELECTOR_DATA_SPY = '[data-bs-spy="scroll"]'; - const SELECTOR_TARGET_LINKS = '[href]'; - const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'; - const SELECTOR_NAV_LINKS = '.nav-link'; - const SELECTOR_NAV_ITEMS = '.nav-item'; - const SELECTOR_LIST_ITEMS = '.list-group-item'; - const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`; - const SELECTOR_DROPDOWN = '.dropdown'; - const SELECTOR_DROPDOWN_TOGGLE$1 = '.dropdown-toggle'; - const Default$1 = { - offset: null, - // TODO: v6 @deprecated, keep it for backwards compatibility reasons - rootMargin: '0px 0px -25%', - smoothScroll: false, - target: null, - threshold: [0.1, 0.5, 1] - }; - const DefaultType$1 = { - offset: '(number|null)', - // TODO v6 @deprecated, keep it for backwards compatibility reasons - rootMargin: 'string', - smoothScroll: 'boolean', - target: 'element', - threshold: 'array' - }; - - /** - * Class definition - */ - - class ScrollSpy extends BaseComponent { - constructor(element, config) { - super(element, config); - - // this._element is the observablesContainer and config.target the menu links wrapper - this._targetLinks = new Map(); - this._observableSections = new Map(); - this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element; - this._activeTarget = null; - this._observer = null; - this._previousScrollData = { - visibleEntryTop: 0, - parentScrollTop: 0 - }; - this.refresh(); // initialize - } - // Getters - static get Default() { - return Default$1; - } - static get DefaultType() { - return DefaultType$1; - } - static get NAME() { - return NAME$2; - } + // TAB DATA-API + // ============ - // Public - refresh() { - this._initializeTargetsAndObservables(); - this._maybeEnableSmoothScroll(); - if (this._observer) { - this._observer.disconnect(); - } else { - this._observer = this._getNewObserver(); - } - for (const section of this._observableSections.values()) { - this._observer.observe(section); - } - } - dispose() { - this._observer.disconnect(); - super.dispose(); - } + var clickHandler = function (e) { + e.preventDefault() + Plugin.call($(this), 'show') + } - // Private - _configAfterMerge(config) { - // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case - config.target = getElement(config.target) || document.body; + $(document) + .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler) + .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler) - // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only - config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin; - if (typeof config.threshold === 'string') { - config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value)); - } - return config; - } - _maybeEnableSmoothScroll() { - if (!this._config.smoothScroll) { - return; - } +}(jQuery); - // unregister any previous listeners - EventHandler.off(this._config.target, EVENT_CLICK); - EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => { - const observableSection = this._observableSections.get(event.target.hash); - if (observableSection) { - event.preventDefault(); - const root = this._rootElement || window; - const height = observableSection.offsetTop - this._element.offsetTop; - if (root.scrollTo) { - root.scrollTo({ - top: height, - behavior: 'smooth' - }); - return; - } - - // Chrome 60 doesn't support `scrollTo` - root.scrollTop = height; - } - }); - } - _getNewObserver() { - const options = { - root: this._rootElement, - threshold: this._config.threshold, - rootMargin: this._config.rootMargin - }; - return new IntersectionObserver(entries => this._observerCallback(entries), options); - } +/* ======================================================================== + * Bootstrap: affix.js v3.3.7 + * http://getbootstrap.com/javascript/#affix + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ - // The logic of selection - _observerCallback(entries) { - const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`); - const activate = entry => { - this._previousScrollData.visibleEntryTop = entry.target.offsetTop; - this._process(targetElement(entry)); - }; - const parentScrollTop = (this._rootElement || document.documentElement).scrollTop; - const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop; - this._previousScrollData.parentScrollTop = parentScrollTop; - for (const entry of entries) { - if (!entry.isIntersecting) { - this._activeTarget = null; - this._clearActiveClass(targetElement(entry)); - continue; - } - const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop; - // if we are scrolling down, pick the bigger offsetTop - if (userScrollsDown && entryIsLowerThanPrevious) { - activate(entry); - // if parent isn't scrolled, let's keep the first visible item, breaking the iteration - if (!parentScrollTop) { - return; - } - continue; - } - // if we are scrolling up, pick the smallest offsetTop - if (!userScrollsDown && !entryIsLowerThanPrevious) { - activate(entry); - } - } - } - _initializeTargetsAndObservables() { - this._targetLinks = new Map(); - this._observableSections = new Map(); - const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target); - for (const anchor of targetLinks) { - // ensure that the anchor has an id and is not disabled - if (!anchor.hash || isDisabled(anchor)) { - continue; - } - const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element); ++function ($) { + 'use strict'; - // ensure that the observableSection exists & is visible - if (isVisible(observableSection)) { - this._targetLinks.set(decodeURI(anchor.hash), anchor); - this._observableSections.set(anchor.hash, observableSection); - } - } - } - _process(target) { - if (this._activeTarget === target) { - return; - } - this._clearActiveClass(this._config.target); - this._activeTarget = target; - target.classList.add(CLASS_NAME_ACTIVE$1); - this._activateParents(target); - EventHandler.trigger(this._element, EVENT_ACTIVATE, { - relatedTarget: target - }); - } - _activateParents(target) { - // Activate dropdown parents - if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) { - SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE$1, target.closest(SELECTOR_DROPDOWN)).classList.add(CLASS_NAME_ACTIVE$1); - return; - } - for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) { - // Set triggered links parents as active - // With both
    and