diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/404.html b/404.html new file mode 100644 index 00000000..d32cdbaa --- /dev/null +++ b/404.html @@ -0,0 +1,2780 @@ + + + + + + + + + + + + + + + + + + + + + Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Button/Button Activation/index.html b/Button/Button Activation/index.html new file mode 100644 index 00000000..cca592d7 --- /dev/null +++ b/Button/Button Activation/index.html @@ -0,0 +1,3614 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Button Activation - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

In the definition of the button, the type attribute determine what the button will do.

+

For a given button on a deck, the action that the button will be able to carry over is limited to a set of valid types. A push button is not capable of rotations of an encoder.

+

When interaction occurs on a button on a deck, Cockpitdecks creates a typed Event, and passes it for activation.

+

Each activation is designed to handle one or more Events types. For example a EncoderPush activation is capable of handling both PressEvent and EncoderEvent. What is does with those event is left to the activation.

+

This file list all activations that are currently available in Cockpitdecks. For each activation, we present its name or keyword by which it must be referred to, the attributes it expects to work properly, the type of events it expects, and internal state values it produces and maintains.

+ +

Activation Steps

+

Each activation always goes through 3 steps.

+

First, the event is handled completely. Most of the time, the root class of activations is activated first, for global handling and checks. Then the activation itself is performed. The activation returns a boolean flag to indicate that it completed successfully.

+

Second, if the activation produces a value, it is written to the set-dataref of the button, if any. This operation can be considered like an second instruction that is performed provided that the button instructed to write the value to the dataref pointed by set-dataref.

+

Third, and finally, if the button activation contains a view command, it is executed. The purpose of the view command if to alter the view in the cockpit, may be to focus on a particular area of the dashboard to control the effect of the activation.

+

Command, Commands, and «Macro Instruction»

+

When specifying attributes for an activation command and commands attributes can be requested. A command is a single command, commands is a list of individual commands, often, the number of commands in the list matters and match the Activation requirements.

+

A command can be:

+
    +
  • a single string naming the command to execute, or
  • +
  • a single command block, with a condition and or a delay, or
  • +
  • a Macro Instruction, that can contain one or more command blocks.
  • +
+

Single Command String

+
    command: sim/map/show_current
+
+

Single Command Block

+

A Command Block is a series of attributes that specify a command to execute and some optional behavior.

+
	command:
+	  - set-dataref: sim/value/to/set
+	    condition: ${sim/position/altitude} 5000 >
+	    delay: 5
+
+

The group of attributes set-dataref, condition, delay is a command block. It is one instruction (set-dataref), a condition to satisfy before executing the instruction, and a delay to wait after the condition is satisfied before executing the command.

+

The condition is evaluated each time one of its parameter changes.

+

Command Block Attributes

+

Command

+

There are currently two attributes that can be used to specify a command to execute:

+
    +
  • command
  • +
  • set-dataref
  • +
+

Either one can be used but not both. (If both are specified, a warning is issued and the command is ignored.)

+

Condition

+

A condition is a formula that specify, when evaluated, if the command can be executed. The condition is evaluated each time one of its constituting simulator value changes.

+

Delay

+

The delay is a value in seconds that specifies how long after the command receives its instruction to execute it actually perform the task. This allows to pause between commands rather than submitting them all simultaneously to the target.

+

Multiple Command Blocks: Macro Instruction

+
    command:
+      - command: AirbusFBW/MCDU1Menu
+      - command: AirbusFBW/MCDU1LSK6L
+        delay: 1
+      - command: AirbusFBW/MCDU1LSK6R
+        delay: 1
+      - command: AirbusFBW/MCDU1LSK1R
+        delay: 1
+
+

In the above example, the single command consists of a series of 4 command blocks. In this case, the single command is called a Macro Instruction.

+

No Activation

+

type: none

+

Button with no activation are button used for display purpose only.

+

Events

+

Any event can be handed over to the No Activation, since it will not be used.

+

State Values

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
State VariableValue
activation_countNumber of time button was activated
last_activatedTime stamp of last activation
initial_valueInitial value in configuration (if any)
current_valueButton current value
previous_valueButton previous value before change
guardedWhether button has a guard on top of it
managedWhether button has managed mode (specific to some cockpits, which means there are alternate display values)
+

Activation Value

+

Among the above State Value that an Activation returns, there always is a special state value named the activation value. The activation value can be accessed by accessing the activation_value attributes in the state values.

+

This value is just a value among the existing state values that gets highlighted because it is the most sensible value used by the activation.

+

For exemple, the activation value for the On/Off activation is the current state of the activation, either On or Off.

+

In the following descriptions, the activation value is highlighted for each activation.

+

For No Activation activation, the activation value is equal to the activation count. This is also the "default" activation value, if nothing more precise is returned, the activation value is the number of activation of that button.

+

The activation value comes to play when the button needs to determine its value. It first look for a formula, then a single dataref, and if there is no formula and no single dataref, the activation value is used.

+

Page

+

type: page

+

When the button is pressed, a deck will load a page of buttons.

+

Attributes

+ + + + + + + + + + + + + + + + + +
AttributeDefinition
pageMandatory. Name of the page to load. The page must be in the Layout of the target deck.
remote_deckOptional. If present, will load the page on the target deck.
+

Events

+

PushEvent and PressEvent can trigger the Page activation.

+

State Values

+ + + + + + + + + + + + + +
AttributeDefinition
pagepage that is currently displayed
+

Theme

+

type: theme

+

When the button is pressed, the main theme will be changed.

+

Attributes

+ + + + + + + + + + + + + +
AttributeDefinition
themeMandatory. Name of theme.
+

Events

+

PushEvent and PressEvent can trigger the Page activation.

+

Push

+

type: push

+

Push button.

+

Attributes

+ + + + + + + + + + + + + +
AttributeDefinition
commandMandatory. X-Plane command that is executed each time the button is pressed.
+
+

Note

+

Command is a mandatory parameter but if no command is necessary a command placeholder value can be used. Command placeholder value are any of the following string:
+none, noop, no-operation, no-command, do-nothing
+They all are ignored and do not trigger any activity in X-Plane.

+
+

Events

+

PushEvent. Please note that PushEvent consists of 2 distinct events, a pressed event (PushEvent with pressed = True), and a release event (PushEvent with pressed = False), when the button is pressed or released respectively.

+

Options

+ + + + + + + + + + + + + +
OptionDefinition
auto-repeatAuto repeat command at specified pace while the button remains pressed.
auto-repeat option accepts a couple of optional values:

- delay: Time (in second) after which the auto-repeat starts, default to 1 second.
- speed: Time (in second) between 2 executions of the command.
+
	options: auto-repeat=3/0.5,dot
+
+

Auto-repeat will start 3 second after the button was pressed, the command will auto-repeat every 0.5 seconds, twice per second. (dot option also set for other purpose.)

+

BeginEndPress

+

type: begin-end-command

+

Push button that will carry the command as long as the button will remain pressed.

+

Long press command should not be confused with auto-repeat commands. A BeginEndPress command in one command that is executed once as long as the button remain pressed. An auto-repeat command is the same command that is executed several times at regular interval (typically once every 0.2 seconds, 5 times per second) as long as the button is pressed.

+
+

Note

+

The BeginEndPress command requires installation of a XPPYthon3 plugin in X-Plane to circumvent a few X-Plane UDP limitations.

+
+

Events

+

PushEvent

+

Only PushEvent can be used to trigger BeginEndPress Activation since both press and release events are necessary to estimate the timing between both events.

+

Attributes

+ + + + + + + + + + + + + +
AttributeDefinition
commandMandatory. X-Plane command that is executed each time the button is pressed.
+

X-Plane will issue a beginCommand when the button is pressed and a endCommand when released.

+
+

Note

+

Please note that the use of longpress command needs the addition of a little plugin to circumvent X-Plane UDP limitations when used through UDP.

+
+

OnOff

+

type: onoff

+

Push button.

+

Events

+

PushEvent, PressEvent, LongPressEvent

+

Attributes

+ + + + + + + + + + + + + + + + + +
AttributeDefinition
commandsOptional pair of X-Plane commands that are executed alternatively. Two commands must be supplied, but the same command can be provided twice.
set-datarefOptional dataref to set On(=1) or Off (=0).

Either attribute can be set or both. In the latter case, the command is first executed and then the dataref is set.
+

State Values

+ + + + + + + + + + + + + +
AttributeDefinition
oncurrent state On or Off
+

Activation Value

+

The activation value for On/Off activation is the current status, either On or Off.

+

UpDown

+

type: updown

+

Cycle Up and Down button.

+

Attributes

+ + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDefinition
commandsX-Plane commands that are executed when pushes increase value, and when pushes descrease value.
stops=3Number of stop values. For example: Stops=3 will give 0-1-2-1-0 cycles, with 3 stops 0, 1, and 2.
initial-valueIf an initial value is supplied, it's sign indicated how the value will evolve.

For example, if the initial value is 1, the next value will be 2 (go up). If the initial value is -1, the initial value will be set to 1, but the next value will be zero (go down).
set-datarefOptional dataref to set the value of the current stop.

Very much like On/Off activation either commands or set-dataref can be supplied or both.
+

The number of supplied commands may vary.

+

If no command is supplied, it is assumed that there is a set-dataref instruction. In this case, the activation runs inside the [0, #stops[ interval and writes the current value to the dataref.

+

If two commands are supplied, they are assumed to be commands to go up and go down when cycling between the values.

+

If the number of commands is equal to the number of stops, and if there are more than 3 stops, then the activation runs inside the [0, #stops[ interval and executes the command corresponding to the current value.

+

Activation Value

+

The activation value for UpDown activation is the current value.

+

State Values

+ + + + + + + + + + + + + + + + + + + + + +
AttributeDefinition
stopsNumber of stops
go_upTrue will increase at next push; False will decrease
stopCurrent value
+

ShortOrLongpress

+

Short or long press has two commands, one that is executed when the button is pressed for less than long-time seconds, and one when it is pressed more than long-time seconds.

+

type: short-or-longpress

+

Attributes

+ + + + + + + + + + + + + + + + + +
AttributeDefinition
commandsTwo commands, the first one is called on short press.
long-timeTime to press the button to activate second command. Default to 2 seconds.
+

Encoder

+

Type: encoder

+

An Encoder is a rotating knob or dial with steps. Steps are often materialised by a little sound or a slight resistance in the rotation.

+

Attributes

+ + + + + + + + + + + + + +
AttributeDefinition
commandsAn Encoder has two commands, one that is executed for each step while turning clockwise, and one for each step when turning counter-clockwise.
+

State Values

+ + + + + + + + + + + + + + + + + +
StateDefinition
rotation_clockwisenumber of times/clicks the encoder was turned clockwise.
rotation_counterclockwisesame.
+

Activation Value

+

The activation value for Encoder activation is the current rotation value, i.e. number of rotation clockwise minus number of rotation counter-clockwise, negative values means there are more counter clockwise turns.

+

EncoderPush

+

type: encoder-push

+

An EncoderPush is the combination of an Encoder and a push button.

+

Attributes

+ + + + + + + + + + + + + +
AttributeDefinition
commandsAn EncoderPush has 3 commands:

1. First command gets executed when it is pushed
2. Second command gets executed when encoder is turned clockwise
3. Third command gets executed when encoder is turned counterclockwise
+

State Values

+ + + + + + + + + + + + + + + + + + + + + +
StateDefinition
turnsNumber of turns, positive is clockwise
cwnumber of times/clicks the encoder was turned clockwise.
ccwsame, counter clockwise.
+

Activation Value

+

Same as Encoder activation.

+

EncoderOnOff

+

type: encoder-push

+

An EncoderOnOff is the combination of an Encoder and an OnOff button.

+

Attributes

+ + + + + + + + + + + + + +
AttributeDefinition
commandsAn EncoderPush has 4 commands:

1. First command gets executed when it is OFF, to turn it ON
2. Second command gets executed when it is ON, to turn it OFF
3. Third command gets executed when encoder is turned clockwise
4. Fourth command gets executed when encoder is turned counterclockwise
+

Options

+ + + + + + + + + + + + + +
AttributeDefinition
dualWith option dual, the activation uses two more commands.

1. First command gets executed when it is OFF, to turn it ON
2. Second command gets executed when it is ON, to turn it OFF
3. Third command gets executed when encoder is turned clockwise and ON
4. Fourth command gets executed when encoder is turned counterclockwise and ON
5. Third command gets executed when encoder is turned clockwise and OFF
6. Fourth command gets executed when encoder is turned counterclockwise and OFF.
+

State Values

+ + + + + + + + + + + + + + + + + + + + + +
StateDefinition
cwnumber of times/clicks the encoder was turned clockwise.
ccwsame.
onIs currently On or Off
+

Activation Value

+

Same as Encoder activation.

+

EncoderLongPush

+

A combination of pushing and turning.

+

Attributes

+ + + + + + + + + + + + + + + + + +
AttributeDefinition
commandsAn EncoderLongPush has 4 commands:

1. First command gets executed when encoder is turned clockwise
2. Second command gets executed when encoder is turned counterclockwise
3. Third command gets executed when encoder is first pushed, then turned clockwise
4. Fourth command gets executed when encoder is first pushed, then turned counterclockwise
+

State Values

+ + + + + + + + + + + + + + + + + +
StateDefinition
cwnumber of times/clicks the encoder was turned clockwise.
ccwsame.
+

Activation Value

+

Same as Encoder activation.

+

EncoderValue

+

type: encoder-push

+

An EncoderValue is an Encoder that increases or decrease an internal value each time it is rotated clockwise or counterclockwise. The value can be written to an X-Plane dataref or used for other pruposes.

+

Attributes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDefinition
initial-valueInitial value. Default to 0.
stepAmount of value increase or decrease.
stepxlAlternate value for step increase or decrease. If the encoder is capable of push action, the push action will switch between the step and stepxl values.
minMinimal value.
maxMaximal value.
formulaOptional. Formula to transform the ${state:button-value} before it is sent to the dataref.
set-datarefOptional dataref to set to the value of the computed value. The value is sent right away, after each encoder activation.
+

State Values

+ + + + + + + + + + + + + + + + + + + + + +
StateDefinition
cwnumber of times/clicks the encoder was turned clockwise.
ccwsame.
valueCurrent raw value
+

Activation Value

+

The activation value of the EncoderValue activation is the computed value of the encoder.

+

Slider

+

type: slider

+

A Slider is a one dimensional cursor that produces a continuous value within a range. The value can be written to an X-Plane dataref, directly, or after a computation.

+

Attributes

+ + + + + + + + + + + + + + + + + +
AttributeDefinition
set-datarefOptional. Dataref to write the value to, if present.
formulaOptional. Formula to transform the ${state:button-value} (value produced by the slider) before it is sent to the dataref.
+

State Values

+ + + + + + + + + + + + + +
StateDefinition
valueCurrent raw value
+

Activation Value

+

The activation value of the Slider activation is the value of the slider.

+

Swipe

+

type: swipe

+

A Swipe is a 2 dimensional movement of a finger on a surface. The event produced consists of the start and end positions of the finger relative to the surface (x, y) and timing information (time stamp).

+

(Currently not used.)

+

The Swipe event has no attribute.

+

State Values

+

The event of a swipe is complex. The entire event is available as last_event.

+

The event has the following structure:

+
ts_start: 123.456
+ts_end: 135.246
+pos_start: (23, 48)
+pos_end: (78, 42)
+
+

State Values

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StateDefinition
start_xStart of swipe position laterally
start_yStart of swipe position vertically
start_tsTimestamp of start of swipe, in microseconds
end_xEnd of swipe position laterally
end_yEnd of swipe position vertically
end_tsTimestamp of end of swipe, in microseconds
+

From the above values, with some tolerence, it is possible to determine whether the finger moved on the surface or not (swipe or touch), and to determine the duration of the contact with the surface.

+

Button Activations for Developers

+

Reload, Stop, or Inspect are special activations for developer.

+

These activations are normally not used during regular operations.

+

New Activations

+

It always is possible to create new activations by extending Cockpitdecks.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Button/Button Attribute Default Values/index.html b/Button/Button Attribute Default Values/index.html new file mode 100644 index 00000000..e5893268 --- /dev/null +++ b/Button/Button Attribute Default Values/index.html @@ -0,0 +1,2956 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Button Attributes - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Buttons are defined by a list of attributes.

+

Some attributes are unique to a button, very specific, and cannot have default values like, for exemple, the button index or a button label attribute.

+

Some other attributes are used by numerous buttons, like for example the color of the label. These common attributes benefit from a sophisticated default value lookup.

+

Attribute Hierarchical Lookup

+

Before we can look up at the attribute evaluation method, we must notice that all objects we manipulate in Cockpitdecks are organized in a hierarchical way.

+
    +
  1. A the highest, top level sits the Cockpit.
  2. +
  3. The Cockpit holds all Decks available to the simulator.
  4. +
  5. Each Deck uses a Layout
  6. +
  7. Each Layout has one or more Pages
  8. +
  9. Each Page can include other Pages
  10. +
  11. Each Page contains all Buttons definitions.
  12. +
+

This hierarchy is very important.

+

As an example, let us find the value of the label-color.

+

First, the button will perform a direct lookup in its attribute. If it finds a label-color in its attribute, it will use it. If it does not find it, it will ask for its default-label-color. The button will ask its parent entity for the default-label-color.

+

So the Page will search for a default-label-color in its attributes. If it finds it, it will use it. If it does not find it, it ask its parent entity.

+

The Deck will search for a default-label-color in its attributes. If it finds it, it will use it. If it does not find it, it ask its parent entity.

+

Finally, the Cockpit will return a default-label-color. If there is no value for the default-label-color, Cockpitdecks will issue an error. It simply means that there is no default value for that attribute and that a value must be supplied by the user in the definition of the button.

+

Theme

+

Cockpitdecks introduced the concept of color schemes or Themes. A theme is an additional parameter (string) that is added to the attribute name being looked up. Let us see in a practical example.

+

Day or Night Theme

+

Cockpitdecks attempts to provide a day and a night theme. The attribute cockpit can be set to day (or light) or night(or dark) to specify which theme to use.

+
cockpit-theme: dark
+
+

The effect is that in night (or dark) theme, default values prefixed with dark- will be favored. If no default value prefixed with dark- is found, the regular default value is fetched.

+

Example for label color

+

If the theme dark is defined, the attribute dark-default-label-color is first searched, and if not defined, the default-label-color is retuned.

+

The word dark is arbitrary. It can be any string. But the attribute named <any-string>-default-label-color will be searched first, and if not found default-label-color will be used.

+
+

Note

+

Ultimately, this scheme can be extended to any theme name value, like airbus, or barbie. However, it is advisable to limit theme default values to global appearance parameters like colors, fonts, textures, and sizes.

+
+

There is no automatic theme switch, but a special activation allows for theme setting.

+

Configuration Files

+

Cockpitdecks behavior and deck appearance are driven by a list of Yaml config action files always called config.yaml.

+
    +
  1. Cockpitdecks internals (cannot be modified)
  2. +
  3. Global configuration file: Cockpitdecks/resources/config.yaml (cannot be modified)
  4. +
  5. Aircraft configuration file: Aircraft/deckconfig/config.yaml
  6. +
  7. Layout configuration file: Aircraft/deckconfig/layout1/config.yaml
  8. +
  9. Page configuration file: Aircraft/deckconfig/layout1/page1.yaml
  10. +
+

Sometimes, configuration values can be specified at different level for a given entity.

+

Cockpitdecks (Application), Cockpits

+

At the highest level, a Cockpit will start with a set of default values provided in its internal code.

+

It will then loads additional parameters in a global resource configuration file. That file is the same for ALL aircrafts. The config.yaml file is located in the home directory of Cockpitdecks software, in the resources folder. It cannot be changed.

+

Next, Cockpitdecks will look for an aircraft specific configuration file, in the deckconfig folder of that aircraft. It will load the config.yaml file of that aircraft, and default values loaded from there will apply to that aircraft only. That configuration file can be used for cockpit designer to specify their requirements and preferences.

+

Decks and Layouts

+

A Deck will start with the configuration attributes supplied by the Cockpit. The Cockpit uses the configuration passed in the the global, aircraft-level configuration file.

+

A deck will load a Layout. When doing so, the deck may read an optional configuration file located in the folder of the Layout it will use. The attributes specified in the layout configuration file will take precedence over those at the deck level.

+

Pages and Includes

+

In addition to button definitions, a Page contains other page-level attributes.

+

When a page includes another page, their respective attributes get melted (combined). The attributes of the included page overwrite the attributes of the base page.

+

Since a page can include more than one other page, the attributes of the included page are added (on top of) the attribute of the base page and other included pages. But since the order of page inclusion is not specified, attributes may be piled up in any order.

+

In other words, it is advisable to not include any page-level attribute in a page that will be included in another page, it may lead to unexpected behavior or presentation. It is safer to limit inclusion to the buttons attribute, where buttons of main page and included page are merged together.

+

Summary

+

Here is an example how attribute value is looked up for a button's bg-color.

+

attr-lookup.png

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Button/Button Index/index.html b/Button/Button Index/index.html new file mode 100644 index 00000000..e6194ffd --- /dev/null +++ b/Button/Button Index/index.html @@ -0,0 +1,3014 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Button Index - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

The index attribute is a mandatory attribute of a Button. It designate a very precise button on the deck. Hence, it defines what the button is capable of performing and how it will be rendered.

+

Button Index is specified in the Deck Type definition of the deck.

+

Indices for Streamdeck Devices

+

streamdeck.png

+

Keys

+

Streamdeck Mk.2, XL, Mini are composed of square icon keys. Image sizes vary with models.

+

Streamdeck + (Plus) as 8 square keys, 4 knobs, and 1 larger LCD capable of touch interactions.

+

streamdeckplus.png

+

Encoders

+

Streamdeck Plus encoders are aligned at the bottom of the deck and have indices e0 to e3.

+

Touchscreen

+

Streamdeck Plus has a 800x100 pixel touchscreen between the keys and the encoders.

+

Indices for Loupedeck Devices

+

loupedecklive.png

+

Keys

+

LoupedeckLive has 6 knobs, 12 square keys (90x90), and 2 side LCD (60x270).

+

There is an additional row of 8 push button labeled 0 (dotted-circle) to 7.

+

Both LCD and all 12 keys form a larger (480x270) LCD capable of touch interaction.

+

In an alternate presentation, side LCD can be considered as 3 individual buttons each.

+

To simplify, we assume in this case that all 6 side buttons are square (60x60). Space between these button is filled with default color, image, or pattern.

+

Indices for Behringer Devices

+

xtouchmini.png

+

Keys

+

XTouchMini has 8 encoders with 8 "multi-led" displays, and 16 buttons with LED.

+

There is a slider and two more buttons on the side. These two buttons were meant to be used as "page" switches between 2 pages labeled A and B. For simplicity, we chose to not change the use of these two buttons and they should be programmed as Page Loader activation. The currently loaded page can be highlighted, like on the above photograph, Page A is loaded.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Button/Button Representation/index.html b/Button/Button Representation/index.html new file mode 100644 index 00000000..87688f2a --- /dev/null +++ b/Button/Button Representation/index.html @@ -0,0 +1,3246 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Button Representation - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

The Representation of a button determine how it will be displayed on the deck device.

+

The representation depends on the capabilities of the button on the deck. There is a list of valid representations or a given button on a deck. A image or icon cannot be displayed on a LED-only button.

+

In the button definitions, the presence of a specific attribute with determine how the button gets represented on the deck. The name of that attribute is the key word of the representation. For example:

+
    +
  • icon: To show a image image
  • +
  • led: To turn a LED on or OFF
  • +
  • annunciator: To display a complex image as an icon
  • +
  • etc.
  • +
+

The first attribute mentioned in each section below determines the type of Representation (icon, text, multi-icons, etc.) If more than one representation is found, or a representation that is not valid for the given button, a warning message is reported and the button does not render anything.

+

If no Representation is found, a warning message is reported and the button is assumed having no representation. For example, a X-Touch Mini slider has no representation. To suppress the warning message, the representation attribute can be used and set to false.

+
  - index: slider
+    name: SLIDER
+    type: slider
+    representation: false
+
+

will not issue any warning message.

+

Representation Attributes

+

A representation often has attributes that customise its behavior.

+

All representation-specific attributes must be declared under the attribute that declares the representation: (See attributes in pink in picture below.)

+

This is a requirement to differentiate attributes at the button activation level (not indented, in blue or green), and button representation (indented, in pink)

+

button-anatomy.png

+

Representations

+

Here is a list of currently available, general purpose representations.

+

Basic Button Representations

+ +

Drawn Representations

+ +

More complex Button Representations

+ +

Deck Specific Displays and Representations

+

Please refer to the following pages for deck specific representtions.
+Deck specific means those representations on one deck will (probably) not work on another deck.

+ +

Aircraft / Deck Specific Representations

+

(To be used as model for alternate development.)

+
    +
  • Toliss Airbus FMA Display
  • +
  • Toliss Airbus FCU
  • +
+

Common Representation Attributes

+

Managed

+

dataref: dataref-path

+

Path to a dataref that is interpreted to determine whether the value is managed.

+

If the value is managed, the value can be displayed as a text string in an alternative way depending on the text-alternate value.

+

text-alternate: dash=4: Represent managed value by a set of -. Default is 3 dashes.

+

text-alternate: dot: Represent managed value by a single dot .

+
  - index: 0
+    type: none
+    name: FCU Airspeed display
+    label: SPD
+    text: ${sim/cockpit2/autopilot/airspeed_dial_kts_mach}
+    text-format: "{:3.2f}"
+    text-color: khaki
+    text-size: 24
+    text-font: Seven Segment.ttf
+    text-position: cm
+    text-bg-color: (40, 40, 40)
+    managed:
+        dataref: AirbusFBW/SPDmanaged
+        text-alternate: dash
+
+

In example above, speed managed mode, if AirbusFBW/SPDmanaged dataref value is non zero, the text will display ---. Otherwise, it will display the air speed.

+

Guard

+

dataref: dataref-path

+

Path to a dataref that is interpreted to determine whether the button or key is guarded (protected against unintentional use by a cap or lock). If guarded, it can be displayed in an alternative way depending on the options value.

+

type: Protects the button with a full red cover (type: full) or a see-through grid (type: grid)(cover is the default).

+

color: Color of the guard. Default is red for cover, and translucent red for grid.

+
    label: RAM AIR
+    guard:
+      type: grid
+      color: black
+      dataref: ckpt/ramair/cover
+      # 0=closed, 1=opened
+
+

Guarded buttons or keys need to be pressed twice to activate, the first activation lifts the guard, the second one acts normally. To replace the guard, a long press of more than 2 seconds is necessary to replace (close) the guard.

+
+

Long Press

+

Make sure long press lasts 2 seconds or more, otherwise the button will be activated!

+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Button/Button Representations/Animations/index.html b/Button/Button Representations/Animations/index.html new file mode 100644 index 00000000..d72d6659 --- /dev/null +++ b/Button/Button Representations/Animations/index.html @@ -0,0 +1,3062 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Animated Buttons - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Typical buttons display an information in a static way, an icon or a LED that is updated when a status or a value has changed.

+

Animation is a button representation option that consists of continuously sending representation updates to continuously change the button's appearance.

+

For example, in its simplest form, a icon animation consist of sending a different images to a key at regular interval, cycling to a list of images (vey much like a GIF animation…).

+

Another simple animation consists of making an icon «blink» on or off. A more complex animation can consist of a parametrized drawing, an image is drawn at each iteration and sent to a key.

+

Animations are automatic. They should not be confused with button appearance updates that occur automagically when underlying button values change.

+

Most of the time, the value button determine if the animation runs. When the value of the button is On, the animation runs. When Off, the animation does not run and may displays an alternate representation.

+

- Animations#IconAnimation

+

IconAnimation

+

An icon-based animation is a procedure that changes the icon to display at regular internal.

+

The icon to display is specified through its index value in the list of icons available to the button (through the multi-icons attribute).

+
icon-animation:
+  - ICON_FILE_1
+  - ICON_FILE_2
+
+

Multiple icon files are displayed in sequence automagically when the button is On.

+

Attributes

+

icon-off: ICON_FILE_NAME

+

The icon to display when the button is Off. If there is no icon-off, the first icon in the icon-animation list is used.

+

speed

+

Time in second an icon is displayed before displaying the next one.

+

Blinking Animation

+

A blinking animation is an procedure that forces some of all part of a Representation alternatively On and Off and provoque a rendering update after changes, leading to a blinking button effect.

+

In this case, the animation controls and changes the value of the button, switching between On and Off states. The representation changes accordingly.

+

Drawing Based Animation

+

A Drawing animation is a parametrized drawing that uses a single parameter value (for exemple the button's value). When the value changes, the procedure optionally uses a tweening algorithm to progressively change the value from the old one to the new one.

+

As a proof of this concept, the FollowTheGreens Representation displays a portion of taxiway centerline with "flashing" lead light when it is activated.

+

Animations do not need to be fast. As another proof of concept, the Weather representation pictures a Metar report on a icon. The representation is updated each time the Metar is updated (about every 30 minutes). It is another form or use of animation.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Button/Button Representations/Annunciator/index.html b/Button/Button Representations/Annunciator/index.html new file mode 100644 index 00000000..012494f7 --- /dev/null +++ b/Button/Button Representations/Annunciator/index.html @@ -0,0 +1,3110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Annunciators - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

An Annunciator is a special image used for display on a deck key.

+

An Annunciator image is build dynamically from its definition and from data coming from X-Plane. Airbus airliners extensively use annunciator buttons.

+

Annunciator do not specify what they do, this is done by the button activation definition. Annunciator only address the representation of the button, the content of the image displayed on a deck key.

+

On a deck, the representation of keys that accepts images can either be

+
    +
  1. One or more icons, the icon being displayed at a given moment is determined by data provided by X-Plane,
  2. +
  3. A switch, which is a dynamically built image of a switch or circular switch,
  4. +
  5. An Annunciator, which is an alternate image, dynamically built from a definition and data provided by X-Plane or button status.
  6. +
+

Annunciator Shapes and Sizes

+

Annunciator Sizes

+

Annunciators exits in 3 sizes:

+
    +
  1. Large, square 1in × 1in.
  2. +
  3. Medium, rectangular, ⅝in × 1in, or smaller but in the 5:8 ratio.
  4. +
  5. Small, rectangular, ½in × 1in, or smaller but in the 1:2 ratio. (Or sometimes 3:8 ratio.)
    +Given the limited size of deck key images (typically less that 100 pixels), annunciator always occupy the maximum space on the key. However, the above size of the annunciator govern the aspect ratio of the image: 1:1, 5:8, 1:2.
  6. +
+

annunciator-sizes.png

+

Annunciator Model

+

Annunciator can display from 1 to 4 different data or information on a single key. Depending on the annunciator model, data is displayed on two rows, in two colums.

+

a-parts.png

+

Each portion of an annunciator that can be used to display information is called an (annunciator) part. In an annunciator of type A, there is only one part called A0. In an annunciator of type E, there are three parts, E0, E1, and E2, arranged like shown on the above illustration.

+

Annunciator of type F can display 4 different informations. The button underlying such an annunciator has therefore 4 distinct values.

+

Annunciator Parts

+

Each annunciator part is defined independently of the other parts.

+

In a part, displayed information is either

+
    +
  1. A Text, which can optionally be framed, or
  2. +
  3. A LED of some kind: Block, bars, dot, or lgear (a small triangle)
    +(Since Cockpitdecks provides icon fonts, (or you can load your own font,) it is possible to display any icon from a font with Text information and optionally frame it.)
  4. +
+

Annunciator Definition

+
  - index: 5
+    name: A/THR
+    type: annunciator-push
+    annunciator:
+      size: medium
+      model: B
+      parts:
+        B0:
+          color: lime
+          led: bars
+          dataref-rpn: ${AirbusFBW/ATHRmode}
+        B1:
+          text: A/THR
+          color: white
+          size: 60
+          dataref-rpn: "1"
+    command: AirbusFBW/ATHRbutton
+
+

The Annunciator defintion starts at the annunciator: attribute.

+

Attributes

+

model

+

Code letter from A to F to specify how annunciator parts are organised on the annunciator.

+

size

+

Annunciator size: large, medium or small. Full size is a large size that occupies the whole square button. Size mini exists but is practically not used.

+

parts

+

The part attribute can be used to group all part definitions.

+

Each part is addressed by the name of the part: A0, B0, B1, etc. The content is the part definition.

+

Part Definition

+
		 B0:
+			color: lime
+	        led: bars
+	        dataref-rpn: ${AirbusFBW/ATHRmode}
+
+

Text or LED

+

The part definition must contain either a text attribute or a led attribute.

+

a-data.png

+

Status On - Off

+

Each part of a Annunciator has its own value. The value of a part is computed like the value of a Button, from datarefs and/or formula.

+

A part is either lit or not, On or Off. Either status can be represented by supplying background and foreground colors.

+

a-status.png

+
+

About Off Color

+

When off, Vivisun style annunciators remain black what ever is asked for dimmed or off-color.
+When off, Korry style annunciator will exhibit either a dimmed representation of the text (or LED, or what ever is displayed) or a "off-color" representation of it.

+
+

Attributes

+

Text

+

Text-format

+

Font

+

Size

+

Color

+

Invert Color

+

Off Color

+

Led

+

Part Data Value

+

Each part of a Annunciator has its own value. The value of a part is computed like the value of a Button, from datarefs and/or formula.

+

Dataref

+

Single dataref used for value.

+

Formula

+

Formula used to determine the value of the part.

+

Annunciator Style

+

There are two styles of annunciators. Both are named after major brands of annunciator manufacturer. Annunciators appears differently according to their style.

+

The first style is Korry (annunciator-style: k), where the annunciator appears like a translucent window with back light. When the annunciator is not lit, the text or drawing is slightly readable on the display. When lit, the text appears to glow.

+

korry.pngkorry-glow.png

+

The second style is Vivisun (annunciator-style: v). When the annunciator is not lit, it has the color of the button (usually black) and no text is readable. When lit, displays on a Vivisun annunciator are sharp, very much like a "retina display" (high resolution display).

+

vivisun.png

+

Both styles truthfully reproduce keys on decks. Combined with the adjustment of the intensity of the deck back light, they provide a real immersive experience.

+

annnunciator-style can be defined at the Cockpit, Deck, or Page level.

+

Guard

+

Annunciators can optionally be protected by plastic cover guards.

+

Guards

+

guards.jpg

+

Guards as Drawn

+

guards.png

+

Attributes

+

dateref

+

Dataref path to value driving the guard status (open or protected).

+

type

+

Cover or grid

+

color

+

Color of guard. Defaults to red.

+

Translucent color (with alpha, or transparency channel) can be supplied.

+

color: (255, 0, 0, 100)

+

Is a translucent red color (r,g,b,a), a=0=transparent, a=255=full opaque.

+

Design Examples

+

a-examples.png

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Button/Button Representations/Basic/index.html b/Button/Button Representations/Basic/index.html new file mode 100644 index 00000000..4e3c49ac --- /dev/null +++ b/Button/Button Representations/Basic/index.html @@ -0,0 +1,3103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Basic Buttons - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + +

Icon

+

icon: ICON_FILE_NAME

+

An image file is loaded on the deck key if it is capable of displaying images.

+

Attributes

+

frame

+
icon: isi
+frame:
+	frame: frame_image.png
+	frame-size: [256, 256]
+	content-size: [128, 128]
+	content-offset: [64, 64]
+
+

A frame is a special "background" image that is first loaded, and the icon itself is laid over the background image. In the above example, icon isi will be resized to 128x128px and placed (pasted, laid over) at position (64, 64) in the frame image.

+

frame.png

+

The purpose of framed icon is to provide a uniform icon representation (the frame) with varying inner content (the icon itself).

+

It is different from a textured background because it allows for icon placement and resizing before pasting over the underlying image frame.

+

IconText

+

text: "SOME\nTEXT"

+

An image file is created with a uniform background color or texture and text laid over.

+

The text laid over the button should not be confused with the label. The text laid over is additional to the label, and can be dynamic to display a changing value like the heading of the aircraft or the amount of fuel left in a tank.

+

Attributes

+

text-bg-color: lime

+

Background color of the image or icon where the text is displayed. Default background color is cockpit color.

+

text-*: value

+

Values for font, font size, color, and position of the text on the image.

+

Text Substitution

+

It is possible to use subsitution of coded string in text values:

+
    +
  • ${dataref-path} is replaced by the scalar value of the dataref pointed by dataref-path.
  • +
  • ${formula} is replaced by the value computed in the formula .
  • +
  • ${state:name} is replaced by the scalar value of the button' state name.
  • +
+

Note: A Text string value is not a formula and is not evaluated. Above strings are simply substituted.

+
text: ${formula}
+text-format: 4.2f
+formula: 3.14 2 ${dataref-path} * *
+
+

Will produce an icon with text value "3.14" text in the middle.

+

MultiIcons

+

multi-icons

+
multi-icons:
+  - ICON_FILE_1
+  - ICON_FILE_2
+
+

Multiple icon files are displayed according to the value of the button.

+

For example, for an OnOff activation type there may be two icons for On and Off positions; for a UpDown activation type, there may be one icon for each stop position.

+

No attribute.

+
+

Multi

+

Cockpitdecks offers representations like multi-icons, multi-texts, and even multi-buttons always on the same pattern. The multi- keyword means that there are several representations of the same type available in the button definition. The value of the button determine which one if chosen for representation. If the value of the button does not point at a valid index in the list, the representation is ignored.

+
+

MultiTexts

+

multi-texts

+
	multi-texts:
+		- text: Option 1
+		  text-color: green
+		- text: Option 2
+		  text-color: amber
+		- text: ${aicraft/some_value}
+		  text-format: "Limit reached {4.1f}"
+		  text-color: red
+		  text-font: DIN Bold
+		  text-position: tr
+	formula: ${dataref_for_text_selection}
+
+

A Multitext attributes contains a list of IconText-like attributes (a list of text block) where each text block can contain attributes like in an IconText text block.

+

A single text block is selected according to the value of the button.

+

In the above example, the formula ${dataref_for_text_selection} return 2, the second text block

+
		  text: ${aicraft/some_value}
+		  text-format: "Limit reached {4.1f}"
+		  text-color: red
+		  text-font: DIN Bold
+		  text-position: tr
+
+

will be selected and displayed as a IconText.

+

Attributes

+

None

+

Important Note

+

The selection of the text block must be performed by a formula and not a dataref.

+

When a formula is present in the attributes of a button, it is always favored over a list of datarefs, even if the list contains only one dataref. (If there a button uses multiple datarefs and there is no formula, the value that is returned for that button is a dictionary of all dataref values.)

+

Multi-Buttons

+

See Multi-Buttons and Mosaic.

+

LED

+

led: led

+

Turns a single LED light On or Off depending on the button's value.

+

ColoredLED

+

led: colored

+

Attributes

+

color: orange

+

Turns a single LED light On or Off depending on the button's value. The color of the LED is determined by the color attribute.

+

The color attribute can use a formula to determine the color (single hue value in 360° circle.)

+

Switches

+

Switches are drawings often used by OnOff activations that can be used in replacement of icons.

+

Switches is a drawing of a simple two or three-way switch.
+Circular switches are multi-value rotation switches or selectors.
+Push switches are simpler two state push button.

+

Deck Specific Displays

+

Some decks have particular LCD displays. Cockpitdecks designed specific activations and representations for those. See here:

+ +

Other Representations

+

Above switches are special instances of dynamically drawn representations.

+

There are other dynamically drawn represetations for displaying data, or weather information.

+

The sky is the limit.

+

Representation Validity

+

Each Representation has a is_valid() method that checks whether all necessary attributes or parameters are available to it and valid. If the validity function fails, a warning is reported and the button is not rendered. The inspect keyword used to verify the validity of the representation is valid.

+

Each Representation has an describe() method that explains what it displays in plain English. The inspect keyword used to describe the representation is what.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git "a/Button/Button Representations/Deck Specific \342\200\223 Loupedeck/index.html" "b/Button/Button Representations/Deck Specific \342\200\223 Loupedeck/index.html" new file mode 100644 index 00000000..44991e4f --- /dev/null +++ "b/Button/Button Representations/Deck Specific \342\200\223 Loupedeck/index.html" @@ -0,0 +1,2948 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Deck Specific Representations – Loupedeck - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

ColoredLed

+

ColoredLed buttons are color capable led behind a transparent or mask button.

+

They are either on or off, with a RGB color.

+
colored-led: blue
+
+

IconSide

+

icon-side: ICON_FILE_NAME

+

An IconSide is a special icon for LoupedeckLive devices, used on either side of the main panel. IconSide have particular display capabilities to cope with their specifics, sizes, and their positions that allow to display information regarding the nearby encoders.

+

Attributes

+

labels

+

Labels that are displayed on the icon.

+

label-positions

+

Label anchor position expressed in percentage of the 100% height of the side image.

+

Note: There might be similar icons to control the display of other, larger display like the Streamdeck Plus bottom LCD display.

+

Larger Displays

+

Some decks exhibit displays that are larger than a key with and iconic representation. Those displays often have touch and swipe capabilities. This led to design activations and representations specific to these larger displays to be used with your favourite flight simulation software.

+
+

Please note, for example, that the LoupedeckLive deck has a unique central display screen accepting touches and swipes. A plastic grid cover gives the illusion that there are two vertical side screens and 12 « keys » in the middle, but underlying is a unique 480 × 360 pixel screen. Each key is a 90 × 90 pixel portion of that screen.

+
+

Usage

+

Larger screens can be used in two different ways:

+
    +
  • As a whole, unique, larger display surface,
  • +
  • As a set of adjacent buttons covering the entire surface.
  • +
+

In the later case, the specificity of the display is not apparent, and resulting buttons are treated as regular keys with iconic image display (with different sizes sometimes.) This is called a Mosaic.

+

When considered as a single larger display, it is difficult to remain generic since each display will have special size, location, and behavior. Cockpitdecks buttons assigned to those specific display are therefore also very specific.

+

Activations

+

Having no activation at all to use those display solely for displaying purposes is always an option. However simple activations can make the passive display a lot more enjoyable.

+

For exemple, if the display is capable, touching or swiping the display can be used to change its content. On startup, display heading, swipe left, display speed, swipe right display heading, etc.

+

Touch

+

Touch is similar to button press activation and can be used as such.

+

Swipe

+

Swipe returns movement start and end position and timing. These values can be interpreted to model a limited set of movements like swipe left, right or up, down. Raw values can also be used as an increasing or decreasing sliding cursor.

+

Representations

+

Given the sizes of each display, representations fitting those displays will always remain very specific.

+

Larger Horizontal Displays

+

Airbus Flight Control Unit

+

The highly specific Airbus FCU representation reproduces the central display with all possible modes.

+

Airbus Flight Mode Annunciators

+

The Airbus FMA displays the five annunciators on top of the Primary Flight Display (PFD).

+

There are two modes of display. The first one make use of a larger, horizontal display and shows all five annunciators next to each other. The second one use five keys with iconic display and show one annunciator on one key. In both cases, data necessary for display is fetched only once, the first annunciator being the master one with all data, other annunciators are slave ones and fetch their data from the master annunciator.

+

Airbus FMA display is a pure display with no activation associated with it.

+

Larger Vertical Displays

+

On LoupedeckLive decks, vertical displays exploit their proximity to the lateral encoder dials to present direct encoder feedback. For example, next to the QNH adjustment encoder, the current atmospheric pressure is displayed. Pushing the encode switches between Standard pressure and local ambiant pressure.

+

Icons

+

Image or drawn icons (especially text messages) are also an alternative way to decorate those displays. One can imagine displaying ATC instructions on a tape display, with swipe actions to acknowledge messages.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git "a/Button/Button Representations/Deck Specific \342\200\223 Stream Deck/index.html" "b/Button/Button Representations/Deck Specific \342\200\223 Stream Deck/index.html" new file mode 100644 index 00000000..0cc485f2 --- /dev/null +++ "b/Button/Button Representations/Deck Specific \342\200\223 Stream Deck/index.html" @@ -0,0 +1,2940 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Deck Specific Representations – Stream Deck - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

ColoredLed

+

ColoredLed buttons are color capable led behind a transparent or mask button.

+

They are either on or off, with a RGB color.

+
colored-led: blue
+
+

The Elgato Stream Deck Neo has two small colored led on each side of the LCD display.

+

Larger Displays

+

Some decks exhibit displays that are larger than a key with and iconic representation. Those displays often have touch and swipe capabilities. This led to design activations and representations specific to these larger displays to be used with your favourite flight simulation software.

+
+

Please note, for example, that the LoupedeckLive deck has a unique central display screen accepting touches and swipes. A plastic grid cover gives the illusion that there are two vertical side screens and 12 « keys » in the middle, but underlying is a unique 480 × 360 pixel screen. Each key is a 90 × 90 pixel portion of that screen.

+
+

Usage

+

Larger screens can be used in two different ways:

+
    +
  • As a whole, unique, larger display surface,
  • +
  • As a set of adjacent buttons covering the entire surface.
  • +
+

In the later case, the specificity of the display is not apparent, and resulting buttons are treated as regular keys with iconic image display (with different sizes sometimes.) This is called a Mosaic.

+

When considered as a single larger display, it is difficult to remain generic since each display will have special size, location, and behavior. Cockpitdecks buttons assigned to those specific display are therefore also very specific.

+

Activations

+

Having no activation at all to use those display solely for displaying purposes is always an option. However simple activations can make the passive display a lot more enjoyable.

+

For exemple, if the display is capable, touching or swiping the display can be used to change its content. On startup, display heading, swipe left, display speed, swipe right display heading, etc.

+

Touch

+

Touch is similar to button press activation and can be used as such.

+

Swipe

+

Swipe returns movement start and end position and timing. These values can be interpreted to model a limited set of movements like swipe left, right or up, down. Raw values can also be used as an increasing or decreasing sliding cursor.

+

Representations

+

Given the sizes of each display, representations fitting those displays will always remain very specific.

+

Larger Horizontal Displays

+

Airbus Flight Control Unit

+

The highly specific Airbus FCU representation reproduces the central display with all possible modes.

+

Airbus Flight Mode Annunciators

+

The Airbus FMA displays the five annunciators on top of the Primary Flight Display (PFD).

+

There are two modes of display. The first one make use of a larger, horizontal display and shows all five annunciators next to each other. The second one use five keys with iconic display and show one annunciator on one key. In both cases, data necessary for display is fetched only once, the first annunciator being the master one with all data, other annunciators are slave ones and fetch their data from the master annunciator.

+

Airbus FMA display is a pure display with no activation associated with it.

+

Larger Vertical Displays

+

On LoupedeckLive decks, vertical displays exploit their proximity to the lateral encoder dials to present direct encoder feedback. For example, next to the QNH adjustment encoder, the current atmospheric pressure is displayed. Pushing the encode switches between Standard pressure and local ambiant pressure.

+

Icons

+

Image or drawn icons (especially text messages) are also an alternative way to decorate those displays. One can imagine displaying ATC instructions on a tape display, with swipe actions to acknowledge messages.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git "a/Button/Button Representations/Deck Specific \342\200\223 X-Touch Mini/index.html" "b/Button/Button Representations/Deck Specific \342\200\223 X-Touch Mini/index.html" new file mode 100644 index 00000000..1d7ce45c --- /dev/null +++ "b/Button/Button Representations/Deck Specific \342\200\223 X-Touch Mini/index.html" @@ -0,0 +1,2999 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Deck Specific Representations – X-Touch Mini - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

EncoderLEDs

+

led: encoder-leds

+

(Specific to the X-Touch Mini Encoders.)

+

MultiLeds are LED-based display that use more than one LED for reporting information.

+

X-Touch Mini encoders, for example, are surrounded by 11 LED that can be lit individually.

+

enc-status.png

+

Attributes

+

led-mode: fan or led-mode: 2; name or number

+

Valid modes are:

+
    +
  1. Single
  2. +
  3. Trim
  4. +
  5. Fan
  6. +
  7. Spread
  8. +
+

The value of the button determine how many leds will be displayed (0 to 11).

+
+

X-Touch Mini MACKIE MODE

+

To send feedback instruction to the deck, two modes of interactions are available: Direct mode, and Mackie Mode. Cockpitdecks uses Mackie Mode which makes deck interaction easier and standard through the MIDI protocol. However, in Mackie Mode, only 11 of the 13 available encoder LEDs are accessible. It is not possible to access LED 0 and 13, only 1 to 12.

+
+

Value Mapping

+

If nothing is specified, raw button value is used.

+

If value_min and value_max are specified in the encoder attribute, the Encoder representation performs a linear mapping between (value_min, value_max) and the range of valid values (0, 10).

+

A Warning is issued if the value is out of the range 0-11.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git "a/Button/Button Representations/Drawn Buttons \342\200\223 Charts/index.html" "b/Button/Button Representations/Drawn Buttons \342\200\223 Charts/index.html" new file mode 100644 index 00000000..b41f69ec --- /dev/null +++ "b/Button/Button Representations/Drawn Buttons \342\200\223 Charts/index.html" @@ -0,0 +1,2996 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Data Buttons Charts, Indicators, Gauges - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Cockpitdecks allows to represent a dataref value on a simple chart, or rather, a simple Sparkline. Up to three Sparklines can be combined on a single chart icon.

+

Sparkline

+

A Sparkline is a chart element that plot a single variable over time.

+

A Sparkline keeps a number of points to be plotted.

+

The maximum number of point that can be plotted is either directly supplied, or determined by the time width of the chart. (Points older that the oldest point on the chart are discarded.)

+

Sparkline Representation

+

The list of time-series data can be plotted as

+
    +
  • Points: A marker at each position
  • +
  • A Curve: A simple line
  • +
  • An histogram: A set of vertical bars
  • +
+

chart-anatomy.png

+

Data Acquisition

+

A Sparkline is based on a value like a button value.

+

The value is either fetched at regular interval or updated each time it changes.

+

Therefore, a chart will be updated either at regular interval, if at least one of the data is fetched at regular internal, or whenever the dataref value has changed.

+

There is a maximum update/refresh rate fixed at 4 Hz (¼ of a second.)

+

Chart Width

+

The horizontal axis of charts is always the time.

+

The maximum time that is displayed on the total width of the chart can be determined either by directly supplying a maximum time width, or by supplying a number of points to keep, spaced at a regular interval.

+

Example

+

sample-3charts.png

+

Performance Notes

+

Charts a little icons that can involve a lot of CPU cycles, from dataref collection, change detection, graph data update and then graph update.

+

We limit the update of graphs to 4 Hz maximum (that is 4 times per seconds).

+

There is a limit on the number of Sparklines (graphs) in a single icon: 3.

+

There is also a limit on the total number of Sparklines that can be use at one point in time: 10, all displayed decks combined.

+

Examples of Use

+
    +
  • Fuel per tank and fuel flow per engine.
  • +
  • Autopilot heading vs. actual aircraft heading.
  • +
+
Internal uses
+
    +
  • Performance data like speed to UDP packet acquisition
  • +
  • Speed of deck rendering
  • +
  • and so on.
  • +
+

See Also

+

Drawn Buttons – Gauges and Tapes

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git "a/Button/Button Representations/Drawn Buttons \342\200\223 Gauges and Tapes/index.html" "b/Button/Button Representations/Drawn Buttons \342\200\223 Gauges and Tapes/index.html" new file mode 100644 index 00000000..7cdb85f3 --- /dev/null +++ "b/Button/Button Representations/Drawn Buttons \342\200\223 Gauges and Tapes/index.html" @@ -0,0 +1,2858 @@ + + + + + + + + + + + + + + + + + + + + + + + Drawn Buttons – Gauges and Tapes - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Cockpitdecks provides a few, drawing-based, icons and animations to complement your cockpit or dashboard.

+

The following icons or animations provide each a limited set of customization attributes. Please recall that Cockpitdecks is not a drawing tool, but rather a template tool with custom icons ready to be used with a few customization attributes.

+

In each case, we will detail its intended use and the limits of the icon provided.

+

Also, these special icons exhibit particular design techniques that can be extended by Cockpitdecks Representation developers.

+
+

Work In Progress

+

These icons are currently basic but do provide their function.
+They have limited customization, but these can be added over time later if necessary or requested.

+
+

Display Tape

+

A Tape is a type of display that appears like a tape being slid under the screen. Here are famous display tapes on an Airbus Primary Flight Display:

+
    +
  • Speed tape on the left,
  • +
  • Altitude tape on the right,
  • +
  • Vertical speed gauge indicator on the far right,
  • +
  • Heading tape at the bottom.
  • +
+

PFD.jpg

+

Tapes are often fitted With colorful marks that represent critical values for the indication being displayed.

+

Display tapes should not be confused with duct tapes, often used to fix aircrafts.

+

duct-tape.jpg

+

Duct tapes have one configuration attribute: duct-tape-colorwith default value silver-grey.

+

Gauge

+

Cockpitdecks provides a small set of customizable gauge. Since gauges are rendered on small, often square, LCD screens, they are very limited in detail.

+

One gauge displays one value, and it updated when the value change.

+

A special gauge is the heading gauge (called rose) which show the heading on a compass. Compass can be adjusted North, on autopilot value, or on actual aircraft value (compass or true).

+

Charts

+

Cockpitdecks provides a limited set of time-based display charts, also often called sparklines.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git "a/Button/Button Representations/Drawn Buttons \342\200\223 Switches/index.html" "b/Button/Button Representations/Drawn Buttons \342\200\223 Switches/index.html" new file mode 100644 index 00000000..990df9a7 --- /dev/null +++ "b/Button/Button Representations/Drawn Buttons \342\200\223 Switches/index.html" @@ -0,0 +1,3125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Switches - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

A few set of special buttons are dynamically drawn based on attributes.

+

Attributes are numerous to allow for design flexibility, however default values will always provide a usable representation.

+

The idea of drawn buttons came after the flexibility discovered when designing Annunciators. All annunciators are drawn buttons: Texts, frames, basic icons…

+

Switch

+

A Switch is a drawing of a two or three-way switch on an image key.

+

Switches are often used by On/Off activations.

+

They can be used in replacement of an icon.

+

Attributes

+

Ticks are marks on the side of the switch to label corresponding positions. The tick underline is the visual line line that connect all ticks.

+
    switch:
+      switch-style: 3dot
+      tick-underline: true
+      tick-label-size: 36
+      tick-label-font: DIN Bold
+      tick-labels:
+        - "ON"
+        - "MID"
+        - "OFF"
+    options: 3way,horizontal
+
+

Switches, Circular Switches, and Push Switches have numerous attributes in common to control their appearance. See Circular Switches for a list.

+

Switch Model

+

switches.png

+

Switches as Drawn

+

switches-design.png

+

Options

+

options: 3way

+

Defines a switch with 3 positions, 2 extremities, and one middle position.

+

options: horizontal

+

Draws and manipulates the switch horizontally rather than vertically.

+

options: hexa

+

Draws an hexagonal base to mimic Airbus switch inserts and fixation instead of a round base (default).

+

options: invert

+

Inverts On and Off positions.

+

Circular Switch

+

A Circular Switch is a rotating knob/switch used to represent a rotation switch. It is displayed on an image key. Circular switches are often used by Up/Down activation to cycle and bounce through the set of possible values.

+

They can be used in replacement of multi-icons.

+
    circular-switch:
+      switch-style: normal
+      down: 20
+      left: 20
+      tick-from: 135
+      tick-to: 315
+      tick-space: 20
+      tick-underline-width: 12
+      tick-color: red
+      tick-underline-color: red
+      needle-color: lime
+      needle-length: 130
+      tick-labels:
+        - "0"
+        - "1"
+        - "2"
+        - "3"
+        - "4"
+        - "5"
+
+

Attributes

+
    +
  • Button-size
  • +
  • Button-stroke-color
  • +
  • Button-fill-color
  • +
  • Button-underline-color
  • +
  • Button-underline-width
  • +
  • Needle-color
  • +
  • Needle-width
  • +
  • Needle-underline-color
  • +
  • Needle-underline-width
  • +
  • Stops
  • +
  • Tick-labels
  • +
  • Label-font
  • +
  • Label-color
  • +
  • Label-size
  • +
  • Tick-color
  • +
  • Tick-width
  • +
  • Thik-underline-color
  • +
  • Thik-underline-width
  • +
  • Label-space
  • +
  • Thik-Space
  • +
  • Top, bottom, left, right
  • +
+

Circular Switch Models

+

circular-switches.jpg

+

Circular Switches as Drawn

+

rotating-selector-design.png

+

Push Button and Knob

+
    +
  • button-size
  • +
  • button-color
  • +
  • button-off-color
  • +
  • witness-fill-color
  • +
  • witness-stroke-color
  • +
  • witness-stroke-width
  • +
+

A PushButton is a simple button that can be used to trigger a command. It can be On or Off, and it's state can be reflected graphically by adjusting its color for instance.

+

Push Button Models

+

Later.

+

Push Buttons and Knob as Drawn

+

knobs-design.png

+

Knob

+

Knobs are circular rotating buttons used to set values by rotating the button clockwise or counterclockwise. Although they can be drawn and « turn » according to dataref values, they cannot currently be used with activations to trigger their rotation. Real, physical rotation knobs must be used instead. (Mimicking a rotation knob with a push button is a difficult task that requires awkward manipulations such as long pushes. We may later offer a possibility to allow for rotation knows on icon button, because the surface of some icon button (LoupedeckLive) reacts to touch and swipes. It would therefore be possible to detect precisely where the button was touched (left, right, up, center…) and assign activations accordingly.)

+

So we shall just say that Knob icons can be used as simple push button, with an alternative representation.

+

See Also

+

Switch Drawing Attributes

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Button/Button Representations/Drawn Buttons/index.html b/Button/Button Representations/Drawn Buttons/index.html new file mode 100644 index 00000000..a041b2fd --- /dev/null +++ b/Button/Button Representations/Drawn Buttons/index.html @@ -0,0 +1,2984 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Drawn Buttons - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

A few set of special buttons are dynamically drawn based on attributes.

+

Attributes are numerous to allow for design flexibility, however default values will always provide a usable representation.

+

The idea of drawn buttons came after the flexibility discovered when designing Annunciators. All annunciators are drawn buttons: Texts, frames, basic icons…

+

Data

+

A DataButton is a particular case of a display only button.

+

data.png

+

A DataButton displays four informations:

+
    +
  1. A single letter or icon from a character font, (we can use icon fonts,)
  2. +
  3. A value, and optionally the trend of the value (rising, decreasing, statuquo)
  4. +
  5. A unit short text (static)
  6. +
  7. An optional percentage bar of the value relative to a 100% value
  8. +
  9. A small text string (typically ~20 characters maximum) called the bottom line.
  10. +
+
  - index: 4
+    name: Fuel Level
+    type: none
+    label: Fuel
+    label-size: 10
+    label-position: ct
+    data:
+      icon-name: "gas-pump"
+      data: 75.4256
+      data-format: "{:02.0f}"
+      data-font: DIN Condensed Light
+      data-size: 24
+      data-unit: "%"
+      data-progress: 100
+      data-trend: 0
+      dataref-rpn: ${sim/aircraft/fuel_level} 10 *
+      bottomline: Go Faster
+
+

Data button representation aims at providing a dashboard-like single value highlighted in a deck key image, very much like common web dashboards.

+

dashboard.png

+

Decor

+

Decor representation displays simple connected lines to populate unused icons. They can be used to display visual helper lines that connect annunciators (bleed air, hydraulics, etc.) The idea behind Decor icons is to provide a quick alternative to blank icons when filling large decks with numerous keys.

+

Decor icon are governed by two parameters type and code. The type is a category of drawings. The code determine which drawing will be made in that category.

+

Lines

+

type: line

+

Decor icons of type line display a single horizontal or vertical line, and corner angles. The code determine which line get drawn.

+

decor.png

+
type: line
+code: H
+
+

H.png

+

Segments

+

type: segment

+

Decor icons of type segment display segments that are present in the code attribute.

+

segments.png

+

For example:

+
type: segment
+code: BGNSIL0123
+
+

will light (turn on) segments B, G, N… 3, which correspond will result in a drawing like the one proposed by the H code in type line above.

+

I0123LBGNS.png

+

Common Decor Attributes

+

Line width

+

width: 10

+

Width of the line.

+

Color

+

color: red

+

Color of the line.

+

Aircraft

+

The aircraft representation displays the name of the aircraft (ICAO type designator) located in the dataref: sim/aircraft/view/acf_ICAO. All 4 (or more) characters are fetched and displayed in the icon. (We currently limit fetching the first four characters only, which should be sufficient for ICAO aircraft code designator.)

+

The representation attribute is

+
    aircraft:
+      text-font: B612-Bold
+      text-size: 32
+
+

If a set-dataref is present, the aircraft representation increases the value of that dataref by one each time the aircraft name changes in the sim/aircraft/view/acf_ICAO. This can be used by other button or by Cockpitdecks itself to be notified of an aircraft or aircraft model change.

+

Weather

+

(Please refer to the dedicated Weather Representation page.)

+

METAR

+

The WeatherButton is a special data button that displays METAR information of the station closest to the aircraft in a small, iconic representation.

+
  - index: 8
+    name: METAR
+    type: weather
+    station: OTHH
+
+

weather.png

+

TAF

+

(To do. Cycle through forecast each time button is pressed.)

+

Current X-Plane Weather

+

Region

+

Aircraft

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Button/Button Representations/Multi-Buttons and Mosaic/index.html b/Button/Button Representations/Multi-Buttons and Mosaic/index.html new file mode 100644 index 00000000..a51669cc --- /dev/null +++ b/Button/Button Representations/Multi-Buttons and Mosaic/index.html @@ -0,0 +1,2992 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Mosaic (Large LCD Touchscreen) - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Multi-Buttons and Mosaic allow simultaneous representation of several buttons.

+

Multi-buttons selects one button to represent from a list of buttons.

+

Mosaic represent them all at once, simultaneously.

+

Mosaic

+

A Mosaic is a trick used to split a large button into smaller ones.

+

A Mosaic can, in some regards, be considered as a special case of a Page. Yes, a small page with its own set of individual buttons.

+

Very much like a Page, a Mosaic contains buttons, with their own behavior (activation) and rendering (representation). However, at the end, all button rendering are sticked together, like tiles on a mosaic, to render on the larger button (the mosaic).

+

Typical example of Mosaic are: LoupedeckLive side buttons: They are larger, 60x270 pixels, and can be "split" into smaller buttons, like a column of 4 60x60 buttons, with 10 pixels between each. A Streamdeck Plus touchscreen, which is 100x800 pixel in size, can either be split into 8 buttons of 100x100 each, or less buttons with more space between them.

+

mosaic-ll.png

+

mosaic-sdplus.png

+

mosaic.png

+

Mosaic representation has been designed so that it requires the minimal adjustment to Cockpitdecks. Here are some design constraints.

+

Activation

+

When touching the Mosaic, we need to know where the contact occurred because we have to determine which tile was activated. Therefore, the only valid activation for a Mosaic is swipe event, because the swipe event reports the coordinates (x, y) where the contact occurred.

+

Similarly, for tiles, for simplicity, we will only allow push and long-push events. We may, in future releases, allow for more events but cases will be requested before we make Mosaic too complicated.

+

In a nutshell, the swipe activation of the Mosaic gets transformed into a push, or long-push representation of a tile.

+

Representation

+

The Mosaic representation will be an image. As its name says, it will be composed of smaller tiles, that are also images.

+

Therefore the representation of a button inside a Mosaic, a tile, must produce an image, like icon, all drawing representations (that indirectly create images), etc.

+

In a nutshell, the representation of the Mosaic is built from the representations of all tiles that are put together at their proper offset position.

+

Deck Type Definition

+

Here is an example of Deck Type definition of a mosaic for the LoupedeckLive left screen.

+
  - name: left
+    action:
+      - swipe
+    feedback: image
+    dimension:
+      - 60
+      - 270
+    layout:
+      offset:
+        - 120
+        - 59
+    options: corner_radius=4
+    # The left button is composed of 3 sub-buttons:
+    mosaic:
+      - name: 0
+        # prefix of mosaic buttons should match name of parent mosaic
+        # names will be left0, left1, left2
+        prefix: left
+        action:
+          - push
+        feedback: image
+        dimension:
+          - 60
+          - 60
+        repeat: [1, 3]
+        layout:
+          offset:
+            - 0
+            - 15
+          spacing:
+            - 0
+            - 30
+
+

In the above example, the left 60 × 270 pixel LCD screen will be split into 3 60 × 60 pixel icons with 30 pixels empty space between themselves.

+

Other Examples

+

One Deck Type can define one or more mosaic for its large LCD displays. For example, a 800x100 pixel large LCD can propose a Mosaic of 4 x 200x100 pixels.

+

If a user prefers an alternate Mosaic of 8 x 100x100 pixels, it is necessary to create another Deck Type, with another name, where the alternate mosaic is expressed.

+

The same device, whether physical or "virtual" (through a Web Deck), can be declined in several Deck Types to offer a modular representation to Cockpitdecks.

+

Button Definition

+

Mosaic

+
  - index: left
+    type: mosaic
+    label: MosaiK
+    mosaic:
+      tiles:
+        - index: m0
+          (...)
+
+

The activation (type) must be mosaic. This activation transforms the swipe event into push events.

+

The representation must be mosaic. The representation collects the representation of each tile and compose the final image that is sent for display.

+

Tiles

+
  - index: left
+    type: mosaic
+    label: MosaiK
+    mosaic:
+      tiles:
+        - index: m0
+          name: RELOAD
+          type: reload
+          icon: RELOAD
+          label: RELOAD
+          label-position: ct
+(more clever and realist button later, this is a working placeholder)
+
+

Representation

+

(picture, later)

+

Multi-Buttons

+

The multi-button representation defines a list of buttons.

+

The value of the main hosting button determine which button gets chosen from the list (index value).

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Button/Button Representations/Toliss Specific Representations/index.html b/Button/Button Representations/Toliss Specific Representations/index.html new file mode 100644 index 00000000..68c6d78c --- /dev/null +++ b/Button/Button Representations/Toliss Specific Representations/index.html @@ -0,0 +1,2934 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Aircraft Specific Representations – Toliss Airbus - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Toliss Airbus Aircrafts A319, A320, A321, and A340 share numerous functions, with particularities or specialties. They share a common set of functions, displays, or dataref values. This has led to the development of very specific buttons representation.

+
    +
  • Flight Control Unit display (in both horizontal and vertical modes)
  • +
  • Flight Management Annunciators display
  • +
+

These buttons are highly specific and would probably not be usable in other aircrafts. However the very specific code used to produce these buttons is an example for alternative development.

+

Toliss Airbus FCU

+

ToLiss FCU representation is actually a set of representations for Loupedeck LoupedeckLive side LCD display. As shown on the capture below, it vertically presents FCU data next to the encoders.

+

ToLiss-FCU-vert.png

+

There also is a horizontal version for the Stream Deck + horizontal display.

+

ToLiss-FCU-horiz.png

+

Toliss Airbus FMA Display

+

The specific ToLiss FMA representation is made for long horizontal displays, like Stream Deck + (plus) LCD display.

+

ToLiss-FMA.png

+
+

Page Switch

+

It is easy to configure the LCD screen to change page between "FCU" and "FMA" allowing both screen to co-exist.

+
+

In the FMA page:

+
  - index: touchscreen
+    type: page
+    page: fcu
+
+

and in the FCU page:

+
  - index: touchscreen
+    type: page
+    page: fma
+
+

Individual FMA

+

The FMA display can also be used to display a single FMA on a smaller square icon. 5 small square icons can be used to display all 5 FMAs.

+

ToLiss-FMA-single.png

+

It is also possible to display a single FMA (center), and loop through all FMAs by pressing on it.

+

Examples of such configuration are provided for ToLiss aircraft and can be used to develop alternate display on smaller decks.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Button/Button Representations/Weather/index.html b/Button/Button Representations/Weather/index.html new file mode 100644 index 00000000..815f0873 --- /dev/null +++ b/Button/Button Representations/Weather/index.html @@ -0,0 +1,2956 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Weather Representations - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Cockpitdecks offers different types of Weather Representation, mainly on icons.

+

These Representations have led to development of highly customized activations and representations to exhibit the potentials of Cockpitdecks.

+

While some Representations remain highly generic, like graphical representation of METAR at the closest station, some others are highly specific like those that represent X-Plane simulated weather.

+

There are two sources of information for weather:

+
    +
  1. External, real weather collected from aviation sources.
  2. +
  3. X-Plane, using the weather as it currently is available in the simulation.
  4. +
+

Real Life Live Weather (now)

+

The WeatherMetarIcon provides the real life Metar currently in use at a location in the simulator.

+

If the location is provided through an attribute, it is used and never updated. If no location is provided, the current aircraft location is used. If the aircraft is cruising, the location is updated approximatively every 5 minutes, or when the closest Metar source changes.

+

If no aircraft location is available, a default location is used (currently EBBR, Brussels Airport, Belgium).

+
weather-metar:
+	station: LFBO
+	update: 30  # minutes
+
+

Reports the current real life weather at LFBO. If the location is not changed, the Metar updates every update minutes.

+
+

Metar is from external source

+

The WeatherMetarIcon reflects the real Metar at the time of collection. It does not necessarily reflect the weather as simulated in X-Plane.

+
+

X-Plane Weather

+

X-Plane weather is flexible and proposed with several variant. We designed a few set of X-Plane specific button representation to present these data sets.

+
    +
  • Weather from X-Plane (from global items like METAR)
  • +
  • Weather informations from X-Plane (from clouds and wind layers)
  • +
+

In addition, X-Plane weather can be local or regional.

+
+

X-Plane Weather

+

X-Plane Weather is currently heavily modified by Laminar. The following representation may not work reliably and may need revisions.

+
+

X-Plane Weather Summary

+
xp-weather-summary
+	mode: region
+
+

Provides the Real weather currently installed into X-Plane in a short, Metar-like summary.

+

This is done by fetching a limited set of values from the weather-related datarefs (temperature, pressure, wind, weather conditions…:

+
    +
  1. pressure: sealevel_pressure_pas,
  2. +
  3. temperature: sealevel_temperature_c,
  4. +
  5. dew point: dewpoint_deg_c,
  6. +
  7. visibility: visibility_reported_sm,
  8. +
  9. wind direction: wind_direction_degt,
  10. +
  11. wind speed: wind_speed_msc,
  12. +
+

There are two modes: aircraft and region.

+

X-Plane Real Weather

+
xp-real-weather
+	mode: region
+
+

Provides a detailed weather report from all weather-related datarefs.

+

There are two modes: aircraft and region.

+

This representation fetches all weather-related datarefs from the simulator (wind layers, cloud layers, weather conditions, more than 100 datarefs) for a location or region and attempt to automagically generate a METAR from the collected data.

+

To collect and monitor such an amount of datarefs, Cockpitdecks uses the X-Plane Web API, only available from release 12.1.1 onwards.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Button/Button Value/index.html b/Button/Button Value/index.html new file mode 100644 index 00000000..03a18f89 --- /dev/null +++ b/Button/Button Value/index.html @@ -0,0 +1,3199 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Button Value - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

A button builds its representation from its value. The value of the button is computed from one or more dataref values returned by X-Plane and/or from some internal state variable values.

+

A Button can have 0, 1, or more than one value in the special case of annunciators or Mosaic (A Mosaic can have two or more buttons represented inside.). Each annunciator part or each button inside a Mosaic has either 0, or 1 value.

+

Each value of a button is either None (no value) or a numeric value (which is most of the time a floating point number). If a button has several values, its value is either a list of values or a dictionary of all individual values, each individual value being None or a number.

+

Activations and Representations of the button knows how to manage the different values contained in the annunciator or Mosaic.

+

X-Plane Datarefs

+

A dataref is the name of a value used by the X-Plane simulator.

+

The value can be a string, integer, or float value, either a single value or an array of (same type of) values. A dataref has a name to access it. Names are organized in a folder-like structure (namespace using / separator). Some datarefs are read-only, some other can be written and modified. Names that start with sim/ are reserved for the simulator internal use.

+

Examples

+ + + + + + + + + + + + + + + +
Dataref nameValueDescription
sim/cockpit/misc/barometer_settingFloatValue of the atmospheric pressure at the aircraft location in inches of mercury
+

There are thousands of datarefs in a running instance of X-Plane. Datarefs drive almost everything in the simulator.

+

When used by a button in Cockpitdecks, the value of a dataref is monitored. Its value is fetched from the simulator at regular interval (typically every second). When a dataref's value has changed, all buttons that depend on that dataref are notified of the change to, for example, update their appearance.

+

To explore datarefs available in the simulator, there is a handy X-Plane plugin called DataRefTool. There are also a few web pages that collect, report, and present them so that they can be searched. The plugin allow not only for inspection of datarefs, but also for inspection and discovery of commands, strings that can be submitted to the simulator to perform some action.

+

For simplicity, Cockpitdecks assumes all individual dataref values are floating point numbers or strings. The reason for this is that as today, X-Plane UDP only returns floating point numeric values for requested datarefs.

+

Dataref Rounding

+

Dataref values can change insignificantly very rapidly. To prevent dataref update and its consequences (update of the button value and its representation) with the value change insignificantly, dataref values can immediately be rounded before they are communicated to Cockpitdecks.

+

Required rounding is expressed in the config.yaml file.

+

The dataref-roundings attribute is a list of (dataref-name, significant decimal digit after comma):

+
dataref-roundings:
+    sim/cockpit/autopilot/heading_mag: 2
+    sim/flightmodel/position/latitude: 8
+    sim/flightmodel/position/longitude: 8
+    sim/cockpit/misc/barometer_setting: 2
+
+

There is a global, application-level, dataref-roundings located in the main Cockpitdecks resources folder. The file should never be changed or touched.

+

It is possible for the aircraft configuration developer to specify particular rounding needs in the main config.yaml file in the aircraft deckconfig folder in the same way. Aircraft roundings will take precedence on global roundings.

+

Dataref roundings only applies to datarefs fetched from X-Plane.

+

Dataref Fetch Frequency

+

Similarly to dataref roundings, it is possible to specify, on a dataref basis, the frequency at which Cockpitdecks will ask X-Plane to send values in UDP packets.

+

The default values is between 1 and 4 times per seconds, 1 to 4 Hz.

+
dataref-fetch-frequencies:
+    sim/cockpit/autopilot/heading: 10
+
+

The above configuration indicates that dataref named sim/cockpit/autopilot/heading should be fetched 10 times per second (which is a lot, if not too much. Please recall that Cockpitdecks drives "deck" devices, not screen! Transfer of images to deck icons occurs on slow serial lines.)

+

X-Plane / Cockpitdecks String Dataref

+

Please refer to this Section for The special handling of string datarefs.

+

Cockpitdecks «Internal» Datarefs

+

Cockpitdecks manages its own set of internal datarefs.

+

All datarefs that have a path or name that starts with a special key word are NOT forwarded to X-Plane but rather managed internally inside Cockpitdecks. Otherwise, they are not different from X-Plane datarefs. They can be set and used like any other datarefs.

+

When a button produces an internal dataref, it's definition mention it clearly so that it can be used by other buttons.

+

The current default prefix for internal datarefs is data:.

+
   set-dataref: data:my-local-variable
+
+# in the same or another button, it can be used like so:
+
+   formula: ${data:my-local-variable}
+
+

In the above example, the prefix data: denotes internal datarefs. The name of the internal dataref is data:my-local-variable.

+

Internal datarefs can be used as inter-button communication, to set a value in one button, and use or read it in another one.

+

Button «Internal State» Attributes

+

When a button cannot fetch its representation from X-Plane, it is possible to use some Cockpitdecks internal variables made available through the button state. Each button maintain its state, a few internal variables that can be accessed in formula.

+

Some state variables are generic, and available for almost every buttons, like for instance the number of time a button was activated. Other state variables are activation specific and listed in the Button Activation page under the activation being used, like for example, the number of times a encoder was turned clockwise.

+

Numeric internal values are accessible as ${state:variable-name} in formula.

+
	formula: {state:button_pressed_count} 2 mod
+
+

Activation Attributes and Activation Value

+

Most state attributes of a button come from the activation. Each activation has a specific set of state attributes. Among these state attributes, there is a more particular attribute, called the activation value, which is the most sensible value produced by the activation.

+

Class Instance Attributes

+

For Cockpitdecks developers, all attributes used in the button, its activation, or its representation class instances are also available as state variables. In this case, the value of the attribute is returned with no type checking.

+
class SpecialActivation:
+    ACTIVATION_NAME = "my-activation"
+    def __init__(self):
+        self.my_value = 8
+
+

When used:

+
my-activation:
+    formula: ${state:my_value} 2 /
+
+

equals 4.

+

Button Value

+

From the above

+
    +
  • X-Plane datarefs
  • +
  • Cockpitdecks internal datarefs
  • +
  • Button internal state attributes, in particular the activation value
  • +
+

which may be called variables, a button provides a final value. This value is supplied to the representation that will provide the button feedback on the deck.

+

The following sections details how the value gets computed from the above variables. The possibilities are:

+
    +
  • Value from a formula (that combines several datarefs and button internal state attributes),
  • +
  • Value from a single dataref,
  • +
  • Value returned by the activation,
  • +
  • A list of values for a representation that requires it,
  • +
  • or finally, a list of all the above variables in a dictionary of values.
  • +
+

Single Dataref Value

+

The value of the button is determined by a single dataref value.

+
dataref: sim/weather/region/atmospheric_pressure
+
+

Combining Multiple Variables with Formula

+

A Representation is driven by a single final value. However, it is possible to compute that final value form a list of dataref values, internal button attributes, and mathematical operations. This is done through a formula attribute. The formula is written in Reverse Polish Notation, a method to write and execute operations on values. Since a formula allows for value transformation, a formula should always produce a value that is directly usable by a representation. The value of a button can be computed from data coming either from X-Plane (through dataref values) and/or from the button's internal state values.

+

Examples of formula:

+
# Simple math (in reverse polish notation):
+formula: ${AirbusFBW/OHPLightsATA34[8]} 2 * floor
+
+# Constant 1; always 1; always True or On
+formula: 1
+
+# Insert 0 = true, 1 = false
+formula: ${sim/cockpit2/switches/avionics_power_on} 1 - abs
+
+# Boolean operation not
+formula: ${sim/cockpit2/switches/avionics_power_on} not
+
+# Boolean operation
+formula: ${AirbusFBW/OHPLightsATA34[8]} 4 eq
+
+# Formula used for display of a value
+formula: ${sim/cockpit/misc/barometer_setting} 33.8639 *
+text: ${formula}
+text-format: "{: 4.0f}"
+
+# The following two lines are equivalent; they both return the same value
+formula: ${sim/cockpit/autopilot/vertical_velocity}	
+dataref: sim/cockpit/autopilot/vertical_velocity
+
+

Only one formula attribute can be used for a button or a annunciator part or a LargeButton button.

+

Expression

+

The formula for computation is expressed in Reverse Polish Notation. The result of the formula is a numeric value (float value that can be rounded to an integer if necessary.) It is intimidating at first to write RPN formula, but once a user get use to it, it actually is equaly easy to write RPN formula and formula with parenthesis. In a nutshell, rather than writing:

+
(8 / 2) + (4 × 5)
+
+

in RPN, we write:

+
8 2 / 4 5 × +
+
+

We place the value we act upon first, then the operation we perform on those values.

+

Operators

+

The following operator have been added:

+
    +
  • % or mod: Pushes reminder of division of last two elements, modulo.
  • +
  • floor: Round element to smaller integer value.
  • +
  • ceil: Round element to larger interger value.
  • +
  • round: Last element rounded to closest integer value.
  • +
  • roundn: Element rounded to last element of stack (forced to interger):
    +1.2345 2 roundn => 1.23
  • +
  • abs: Absolute value of last element
  • +
  • chs: Change sign of last element
  • +
  • eq: Test for equality of last two elements. Pushes 1 for True, 0 for False on the stack.
  • +
  • not: Boolean not operator, insert 1 if it was 0 or 0 otherwise.
  • +
+

Variable Substitution

+

In formula:

+
    +
  • ${dataref-path} is replaced by the scalar value (converted to float) of the dataref pointed by dataref-path. Example: ${sim/aircraft/fuel/tankleft}.
  • +
  • ${state:name} is replaced by the scalar value of the (current) button' state variable named name. Names of available state variables depend on the activation; each activation lists internal state variables made available through the button' state. Example: ${state:activation_count}.
  • +
  • ${button:cockpit-name:deck-name:page-name:button-name:button-variable-name}: Substitute de given button name by its value. Example: ${button:Airbus A321:sd-xl:efis:apu:status:activation_count} . If no button variable name is given, the current value of the button is returned.
  • +
+

In all case, if the value is not found, it is replaced by None, which translate into 0 (zero) in formula (to prevent the formula from failing to compute). If the value is not found, a warning message is reported.

+

The following formula determine the final status On(=1) or Off(=0) from the number of times a button was pressed:

+
formula: ${state:activation_count} 2 %
+
+

Activation Value

+

If a button does not reference a dataref, and has no formula, the activation value can be used if it is available.

+

Multiple Button Values

+

In case a button has multiple values, each value comes from a part of the button. Each part of the button is independent of other parts of the same button. Each part maintains its single value.

+

All part values are aggregated into either a dictionary or an array of values that is made available at the button level.

+

For example, Annunciator, or LargeButton representation, have more than one individual values that are fetched and maintained to provide a single table (or array) of individual values.

+

Another example is a button that has more than one dataref and no formula. In this case, the returned value is a dictionary of all dataref values of that button.

+

Button Initial Value

+

A Button can force its first, initial value to set its startup or original state.

+
	initial-value: 2
+
+

This value is assigned as the button's current value on startup.

+

In case of a Button with multiple values, each value has a separate initial-value attribute.

+

Button with No Value

+

Some button may not maintain any state or use any value. Example of such button are simple push button with no feedback mean.

+

Summary

+

The following depicts how a button's value is computed:

+

compute value.svg

+

Notes about Value (Re-)Computation

+

The value of a button is updated at the following moment:

+
    +
  1. When the button is initialized, a first value is computed if not supplied.
  2. +
  3. When a dataref on which the button depends has changed.
  4. +
  5. After an activation.
  6. +
+

An activation only modifies its internal state and does not "forward" its modification to the button. It is the button's responsibility to fetch the value it needs in the activation, through, for example, a state variable:

+
formula: ${state:counter-clockwise-movement-count}
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Button/Set Dataref/index.html b/Button/Set Dataref/index.html new file mode 100644 index 00000000..de3b6de9 --- /dev/null +++ b/Button/Set Dataref/index.html @@ -0,0 +1,2923 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Data Value Change - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

The following document explains the special set-dataref attribute that can be used in numerous button definition.

+

Set-dataref is an instruction that tells Cockpitdecks to set the value of the data pointed by the set-dataref attribute value to the value of the button.

+

set-dataref.png

+

Theory of Operations

+

The set-dataref attribute points at a writable dataref. After each activation of a button, the value of the button is computed and written to that dataref.

+

Button Value

+

When a button uses or produces a single value, that value gets written to the set-dataref.

+

When a button produces more than one value (for Annunciators, buttons without representation, etc.), the set-dataref needs additional information to know which value to write. In the latter case, a formula is mandatory to select the appropriate value to send to X-Plane.

+

Self-Modifying Button

+

In some case, the button is using the same dataref for representation and set-dataref.

+

Here is the simplest example of this case:

+
- index: 0
+  type: push
+  text:
+    text: ${formula}
+  formula: ${data:activation_count}
+  set-dataref: data:internal_counter
+
+

The indent of the above button is to display a counter of how many times the button was pressed. In this example, it appears clearly that the order of operation must be:

+
    +
  1. The button is pressed, the activation is triggered.
  2. +
  3. The activation executes the push instruction AND INCREMENT the activation counter.
  4. +
  5. It is only after the counter that has been incremented that the formula is evaluated and the new value of the button is computed
  6. +
  7. The new value of the button just computed is optionally saved in the dataref pointed by set-dataref.
  8. +
+

So, in case the value of button is computed from one or more values that are very precisely modified by the activation of the button, the modification of the values is registered first, and the final value of the button computed afterwards, just before it is optionally written to the dataref pointed by the set-dataref attribute.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Button/The New View Attribute/index.html b/Button/The New View Attribute/index.html new file mode 100644 index 00000000..4820a372 --- /dev/null +++ b/Button/The New View Attribute/index.html @@ -0,0 +1,3139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + « Commands » - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

A button view attribute is originally a X-Plane command that is submitted after the activation completed, as an additional command. The original intend is to propose a new view after some action to speed up information collection in the cockpit. For example, when the pilot selects APU on the ECAM display, the view attribute specifies a command to change the view and focus on the ECAM display.

+

To achieve this, the view attribute value is a (single) X-Plane command to execute:

+
view: x-camera/view/8
+
+

This document explains a new expression for the view attribute value to allow for the execution of one or more commands.

+

This schema has been extended to other definitions that use a command attribute. It is an alternate way to specify a command. Rather that a single string expression:

+
command: sim/view/map_display_toggle
+
+

it is now possible to express an entire block of pseudo code instruction:

+
# Instruction to display the map if it is not visible.
+# The instruction will be hide the map, just show it if not visible.
+command:
+   command: sim/view/map_display_toggle
+   delay: 5
+   formula: ${sim/view/map_is_visible} not
+
+

Syntax

+
view:
+  - command: command/to/execute
+    delay: 2
+  - set-dataref: dataref/to/set
+    formula: ${dataref/value} 2 +
+
+

The new view attribute value is a list of command blocks.

+

Each command block is a command, an action that will be executed, and a few additional optional parameters.

+

Command Block

+

A command block contains a command which can be:

+
    +
  • An X-Plane command,
  • +
  • An instruction to write a value in a dataref.
  • +
+

In the latter case, the dataref being modified can either be a regular X-Plane dataref, or a Cockpitdecks internal dataref.

+

Command

+
command: command/to/execute
+
+

Set Dataref

+
set-dateref: dataref/to/set
+
+

If the set dataref does not have any other attribute, the value will be set to the value of the button.

+

However, it is possible to specify an alternate value to write:

+
set-dateref: dataref/to/set
+formula: ${dataref/value} 2 +
+
+

To write another value than the button value, the set-dataref attribute must be accompanied by a formula attribute. The value resulting from the formula computation will be written to the dataref.

+

Options

+

The following attributes can complement to above command.

+

Delay

+

If specified, a delay is added after the execution of the last command and before the execution of this command.

+
command: command/to/execute
+delay: 5
+
+

Command above will be executed 5 seconds after the execution of the activation or 5 seconds after the execution of the previous command.

+

Condition

+

A condition attribute is a formula that is executed before he command. If the result of the formula is evaluated to a non null (non zero) value, the command is executed. Otherwise, the command is not executed.

+
command: command/to/execute
+condition: ${some/dataref/to/check} 1 -
+
+

If the result of the computation of the formula is non zero, the command will be executed.

+

Processing

+

Detection of the new parameter value is easy. Either it is a string representing a command to submit to X-Plane, or it is a list of one or more command blocks. There is no ambiguity.

+

Usage

+

The view attribute can be used as a test bed for execution of more than one command in a sequence, a kind of execution of « macro » command, with additional features like conditions and delay of execution.

+

Example of use:

+

When executing a command to trigger a status view, you can imagine the following sequence:

+
    +
  1. Regular button: perform the action.
  2. +
  3. View: move the camera to control the effect of the action.
  4. +
  5. Wait a few seconds… (delay).
  6. +
  7. Restore the original view if there was no alarm (this is a condition of execution).
  8. +
+

Other sequences can of course be imagined. Literally, the sky is the limit.

+

This new view attribute value and its development paves the way of a new, more complex, more flexible action definition.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Button/index.html b/Button/index.html new file mode 100644 index 00000000..14dd1d4c --- /dev/null +++ b/Button/index.html @@ -0,0 +1,3027 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Button - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

A button is the general term for a key, knob, rotary encoder, slider cursor, or even touch surface on a deck. On a given deck, each element that can be pressed, turned, swiped, or slid is a button.

+
+

May be the term Interactor would have been better, more abstract, more generic, but he first deck we used only had keys to be pressed, so we sticked to the term Button, and extended the interactions a simple Button allow.

+
+

For a given deck, all buttons that are available and/or displayed at a moment in time are on the same Page, the collection of buttons currently usable on that deck. Hence, in the definition of a page, there is a mandatory buttons attributes that lists all buttons on that page.

+

In that list, each button is defined by a list of attributes that will determine what it does and how it appears on the deck. The list of attributes that define a button is called a Button Definition.

+

Button Definition

+

The Button Definition is a list of attributes that describe what the button will do when it is manipulated and how it will be represented on the deck if the deck can some how represent the state of that button.

+

Button definition can be very simple and straightforward, but definitions can also be complex and refined.

+

Here is an example of a simple definition of a button to toggle the map display in X-Plane.

+
index: 0
+type: push
+command: sim/map/toggle_map_display
+icon: map
+
+

The definition of a button can be organised into 4 distinct parts:

+
    +
  1. What the button is (and where it is on the deck)
  2. +
  3. What it does (when the button is pressed on the deck)
  4. +
  5. What it displays (on the deck)
  6. +
  7. Where does it get the value it displays from.
  8. +
+

Here is a complex definition of a button which exemplifies all above parts (in different colors):

+

button-anatomy.png

+

Resulting button:

+

button.png

+

The following Sections describe the four button definition parts in detail.

+

Common Button Attributes

+

(In the Anatomy of a Button above, this refer to the blue part of the definition.)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDefinition
indexMandatory. There is no default value.

Each «Button» on a deck is designated by its Button Index. So the index designate a very precise button on the deck.
On a simple deck with a number of similar keys, the index of a button is its ordering number: 0, 1, 2… until the number of keys is reached. On a more complex deck, with button and knobs, knobs may be indexed with name like knob0, knob1, knob2…
nameOptional. A button can be named to ease its identification.

The name of a button on a page must be unique. If more than one button have the same name, an error is reported and the definition of the button is ignored.

If no name is provided, a unique, long, technical name is created from deck name, page name, and index.
typeMandatory.

The type of a button defines what the button will do and how it will be used.

The Button Activation describe button-type specific attributes. In other words, depending on the value of the type attribute, other button defining attributes will be expected.

For example, if the value of a button type is page to change a page on a deck, it is expected to find the attribute named page which contains the name of the page to switch to when pressed.
labelThe label of a button is a short reminder of what the button does. The text of the label is laid over the button image if any. The labelling of a button uses the following attributes:

Note: The Button Label should not be confused with Button Text. The Label exist for all buttons, and is displayed according to its attributes if the underlying button is capable. The text of the label is defined as a button attribute and is static (cannot be changed dynamically).

The Button Text is a text that is part of the Button representation.
label-colorSee Resources#Colors.
label-fontSee Resources#Fonts.
label-sizeIn pixels. Internally, Cockpitdecks uses 256 × 256 pixel images.
label-positionThe position of the label is a 2 letter code:

1. l, c, or r for left, center, or right-justified on the image (horizontal alignment),
2. t, m, or b, for top, middle, or bottom of the image (vertical alignment).
optionsRegularly, buttons have additional parameters.

The button options parameter is a string of comma separated options. An option is either a simple string or word, or a name=value string.

Options are, by nature, not indispensable to the button’s activation or rendering but rather add to it to alter behaviour or appearance.
viewThe view attribute is an additional optional instruction that is executed after the activation. The idea behind it is to first perform the action and then change the simulator view to focus on an area of interest and the activation has completed.
+

Button Value

+

(In the Anatomy of a Button above, this refer to the yellow part of the definition.)

+

A Button has a value that is maintained and used mainly for its representation.

+

The value of a button can come from two sources:

+
    +
  • The simulator (i.e. some variable or parameter used and controlled by the simulator)
  • +
  • The internal state of the button (read below)
  • +
+

The following attributes are used to determine a button’s value:

+

dataref : A single dataref value.

+

formula: An expression that contains variables, including datarefs, and mathematical operations to combine and compute a single final value.

+

multi-datarefs: A list of two or more datarefs. The values of all datarefs in the list will be returned as the value of the button. In this case, the value will be a composite value. The representation that uses such a specific value will know how to use each part of the composite value.

+

Value from the Simulator

+

When the value of a button is computed from one or more values coming from the simulator, Cockpitdecks will request the values from the simulator, and each time one of these values changes, Cockpitdecks will notify the button of the changes so that it can adjust its representation.

+

Button State

+

Finally, in addition to the above attributes that can be used to specify the value of the button, a button has a set of internal attributes that can also be used to determine its value.

+

Each button maintain an internal state: How many times it is pressed, released, turned clockwise or counter-clockwise, what is it current value, its previous, or last value, when it was last used or refreshed, etc.. State information can be accessed by Button designer to control the button behavior and its representation, or its value.

+

Examples of internal state attributes are:

+
    +
  1. activation_count (number of time button was «used»)
  2. +
  3. current_value
  4. +
  5. previous_value
  6. +
+

Internal state attributes varies depending on the button activation. Each activation type returns its own set of particular state values.

+

All these attributes can be used either individually or combined in a formula to determine the value of a button.

+

Please head here for details about the computation of the value of a button.

+

Button Initial Value

+

A Button can force its first, initial value to set its startup or original state.

+
	initial-value: 2
+
+

This value is assigned as the button's current value on startup.

+

In case of a Button with multiple values, each value has a separate initial-value attribute in its own attribute list.

+

Button Activation

+

(In the Anatomy of a Button above, this refer to the green part of the definition.)

+

The type attribute of a button determine how the button will behave, what it will do when pressed, turned or slid.

+

Set-Dataref

+

A button definition can have a set-dataref attribute that points at a Dataref name.

+

If present, Cockpitdecks will set the value of that dataref to the value of the button each time the value of the button changes.

+

Here is example of use. If a button has a activation type of updown with let us say 3 stops, the value of the button can be 0, 1, or 2. Each time the user presses the button the value of the button cycles between those three values.

+
type: updown
+stops: 3
+set-dataref: toliss/NDmodeFO
+
+

If there is a set-dataref attribute, the current value of the dataref (toliss/NDmodeFO) in the simulator will be set to the value of the button: 0, 1, or 2.

+

For some activations, the set-dataref attribute is a mandatory attribute.

+

For some other activations that expects a command to be performed upon activation, the set-dataref attribute (or instruction) can be an alternative to the command. In other words, the commands that gets executed is very precisely the set-dataref instruction.

+

Button Representation

+

(In the Anatomy of a Button above, this refer to the pink part of the definition.)

+

The representation of a Button determine what and how the button will display on the deck device. This depends on the capabilities of the button on the deck: LED, image, coloured led button, sound…

+

The representation of a button is determined by the presence of a special attribute in the definition of the button. That attribute will determine how the button will be represented.

+

For example, if a button definition contains an attribute named annunciator, the button representation will be an Annunciator. A button can only define one representation in its definition. Otherwise, a warning is reported and the button is ignored.

+

Attributes related to the representation of the button are indented under the attributes that names the representation. This is done on purpose to clearly separate attributes dedicated to what the button does (activation), and how it provides feedback to the user (representation).

+

A Page of 32 buttons 4 rows of 8 buttons) can quickly become quite large and difficult to read. Fortunately, recall that a Page can include other Pages to structure the creation of a layout. For example, it might be advisable to create a « main » page with global settings, and include four sub-pages, one for each row on the deck.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Cockpit/index.html b/Cockpit/index.html new file mode 100644 index 00000000..0476e6a8 --- /dev/null +++ b/Cockpit/index.html @@ -0,0 +1,3071 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Cockpit - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Cockpitdecks is an application that connect decks to the X-Plane flight simulator. On one side, the application scans for decks connected to the computer and prepare them for use with X-Plane. On the other side, Cockpitdecks connects to X-Plane through the network to issue instructions to the simulator and listen to changes to reflect those changes on the deck device if it allows it.

+

To determine what to display on decks, which commands to issue to the simulator, etc. Cockpitdecks reads a set of configuration files on startup.

+

intro-detailed.png

+

Configuration files are specific to an aircraft. Commands are different on a Cessna and on an Airbus. Things to display on the deck are different as well. That’s why configuration files are located in the folder of the aircraft being used because they are specific to it. Numerous other software like X-Camera proceed in a similar way, locating their aircraft specific configuration there as well.

+

The Cockpit is the Maestro component of the Cockpitdecks application.

+

It starts up the entire Cockpitdecks application. It establishes connection to the simulator, scans for existing decks connected to the computer, and load the aircraft configuration files. It listen to interactions that occur on the decks and listen to simulator changes to reflect deck statuses. It also monitors which aircraft is currently loaded in the simulator, and if the user changes aircraft, it loads the new configuration if any.

+

The following pages describe the necessary configuration files, their location and organisation, and their content. Configuration files are organized in structured folders, and this structure is explained as well.

+

All configuration files for Cockpitdecks are Yaml-formatted files. Yaml file contains a structured list of (name, value) pairs. The name is referred to as an attribute. The value can be almost anything: A number, a string, a list of things, or a list of other attributes.

+

Cockpitdecks Configuration for one Aircraft

+

Decks are particular to one aircraft. All files necessary to Cockpitdecks are located in a single folder named deckconfig that is found in the folder of the X-Plane aircraft currently being used. In that folder, Cockpitdecks will find all its configuration files.

+

The deckconfig Folder

+

The overall structure of the files and sub-folders inside the deckconfig folder is as follow:

+
<X-Plane Aircraft Folder>/
+  (.. aircraft files ..)
+  deckconfig/
+    config.yaml
+    secret.yaml
+    resources/
+      icons/
+        icon-off.png
+      fonts/
+        B612-Regular.otf
+        DIN.ttf
+      decks/
+        images
+        types
+      docs/
+        README.md
+      other-resource.py
+    layout1/
+      config.yaml
+      page1.yaml
+      page2.yaml
+      ...
+    ...
+    layoutN
+      page1.yaml
+      page2.yaml
+      ...
+
+

The deckconfig folder contains the following files and sub-folders:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescription
config.yamlMain configuration file
secret.yamlSerial numbers of decks used by Cockpitdecks
resourcesResource files used by this configuration. Resources are icons, fonts, images, etc.
layout(s)A Layout is a folder that contains what is displayed on a deck. There can be as many Layout Folder as necessary for this aircraft. All remaining folders in the deckconfigfolder are layout folders. There usually is one Layout folder per deck.
+

config.yaml File

+

The file named config.yaml in the deckconfig folder it the main configuration file, It contains declarations for each deck that will be used, and global, aircraft-level attributes.

+
# Definition of decks for Toliss A321
+#
+aircraft: Toliss 
+decks:
+  - name: XPLive
+    type: loupedeck
+    layout: live
+    brightness: 70
+# These attributes are default values at global level
+named-colors:
+    COCKPIT_BACKLIGHT: darkorange
+default-wallpaper-logo: Airbus-logo.png
+default-icon-color: (94, 111, 130)
+default-label-color: white
+default-label-font: DIN.ttf
+default-label-size: 14
+cockpit-color: lightblue
+
+

Attributes

+

The following attributes apply to all decks for the given aircraft. Each individual deck will have the possibility to redefine these values if necessary.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDefinition
aircraftOptional information. The name of the aircraft for this set of deck.
named-colorsAllows to introduce your own named color. You can then use this name as a color. The value of the named color can either be a 3 or 4 value tuple (r, g, b, a), or the name of a Pillow color. (See Resources.)
cockpit-colorColor for the cockpit. This color is used as the default background color for icons.
cockpit-textureName of a image file (JPEG or PNG) that will be used as the (default) background of icons.

The cockpit-texture file will be searched at different places depending on where it is specified.

The cockpit-texture file can be specified at the Cockpit, Deck, Page, or Button level.

Cockpit-level default textures will be seached in the following folders (in that order):

- resources
- resources/icons

If the AIRCRAFT is specified, Cockpit-level textures and all other levels textures will be searched in the following folders:

- AIRCRAFT/resource
- AIRCRAFT/icons

If no texture is found, a uniform cockpit-color icon is used.
default-wallpaper-logoName of image file, located in the resource folder, to be loaded when the deck is not used.
default-*Name of default values of several parameters, defined at the aircraft-level. These values will be used for all missing values. They can be raffined at Layout and Page level if necessary.

If no aircraft-level global parameter values are not provided, Cockpitdecks will use its own internal default values.
decksA list of Deck structure, one per deck.
+

Yaml allow for other attributes in the file. They are ignored by Cockpitdecks. You may include other attributes like aircraft name, ICAO code, descriptions, notes, even change log of your file. Comments are also allowed in Yaml files.

+

secret.yaml File

+

The secret.yaml file contains the serial numbers of your connected decks.

+

If you have more than one deck of the same type (i.e. two Streamdecks, two X-TouchMini, etc.) this file is mandatory to distinguish between the two physical devices. Otherwise, it is optional.

+
# My decks and their serial numbers.
+# Format:
+# DeckName: Serial number
+# DeckName must match name given in config.yaml.
+XPLive: AAA0000000000000000000A0000
+
+
+

Serial Numbers

+

We experimentally noticed that serial numbers as displayed on the device and as reported by some software package do not always correspond. This also depends on the operating system. As a practical illustration, for HID devices, the request for its serial number throught HID specific protocol calls returns a value WA1234MHK0G, while the core operating system raw USB device probe returns A00WA1234MHK0G. Some device do not respond to serial number requests and in this case, an arbitrary software dependent value is returned, and not guaranteed to be unique(!).
+As a consequence, Cockpitdecks maintains a list of valid serial numbers for a given device.

+
+

Resources

+

Resources are fonts, icons, other images, wallpapers, documentation, and texts used and related to that aircraft. All these elements are organized into the resources folder.

+

Usually, the resources folder contains the following sub-folders:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FolderContent
iconsFolder containing all icon images for that aircraft. Images should be in JPEG or PNG format. Typical icon size is 256 × 256 pixels, RGB(A). Icons are named after their file name without the extension.
fontsFolder containing all fonts for that aircraft. Fonts can be Truetype or Opentype. Fonts are named after their file name with the extension.
docsDocs folder may contain documentation files, like explanatory images, and descriptive texts. Simpler text or markdown files are preferred.
decksCockpitdecks allow experienced users to create their own Web Decks specific to an aircraft.
+

Layout Folders

+

Next to the the above resources folder, there will be one folder per Layout for a deck.

+

Layout folders are explained later.

+

Decks

+

One of the most important attributes in the main configuration file is the decks attribute which defnies all decks available to Cockpitdecks..

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Concepts/index.html b/Concepts/index.html new file mode 100644 index 00000000..bc236738 --- /dev/null +++ b/Concepts/index.html @@ -0,0 +1,3271 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Concepts - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

The following page describe the key components of Cockpitdecks and its vocabulary.

+

Cockpitdecks Objects

+

Walking though Cockpitdecks main entities will get you familiar with Cockpitdecks vocabulary. Main objects have familiar names and do represent what their name designate.

+

Deck

+

The core object of Cockpitdecks is the Deck. It can be a real, physical deck device like Stream Deck or LoupedeckLive, but it can also be the representation of a deck in a web page. In the later case, it is called a Web Deck. Web deck can either be a representation of an existing physical deck like Stream Deck or LoupedeckLive, and an imaginary one.

+

A Deck usually has buttons that can be pressed, encoders that can be turned. They often also have either simple led that can be turned on or off, or small iconic LCD screens where some iconic image can be displayed.

+

The goal of Cockpitdecks is to animate these devices to use them with a flight simulation software.

+

Page

+

A Page is a collection of buttons that are displayed on a deck and ready to be used.

+

Layout

+

A Layout is nothing else than a group of related pages, displayed alternatively on a deck.

+

Button

+

A button is the general term for a key, knob, rotary encoder, slider cursor, or even touch surface on a deck. On a given deck, each element that can be pressed, turned, swiped, or slid is a button.

+

Activation

+

The activation of a button determine what it will do when pressed, turned or slid.

+

Representation

+

The representation of a Button determine what the button will display on the deck device. This depends on the capabilities of the button on the deck: LED, image, colored led, vibration, sound…

+

Cockpit

+

The Cockpit is where everything occurs !

+

The Cockpit is a container entity, the maestro that orchestrate the symphony.

+

Cockpitdecks proceeds by numerous autonomous pieces of program that do some very precise task independently of each other, like listening to what happens on a deck or in the simulation software, and the Cockpit is responsible to orchestrate all these pieces of program so that they work in harmony to timely display information on decks or issue commands to simulation software.

+

Configuration

+

Cockpitdecks configuration is a folder (always named deckconfig), organized into sub-folders, where the Cockpit will find all instructions to execute, both on the simulator side and on the deck side.

+

Cockpitdecks « Internal » Objects

+

Internally, Cockpitdecks uses a few objects whose function are easy to understand.

+

Data

+

A data is a named entity ready to contain some data, a value.

+

There are several types of data depending on where the data gets its value from.

+

Cockpitdecks internal data is an internal value only used by Cockpitdecks, to maintain an internal state for example.

+

Simulator data is a value coming from the simulation software. Each time it changes in the simulation software, its value is updated in Cockpitdecks.

+

Data Listener

+

Each data has listeners associated with it. Each time the value of a Data changes, the listeners are notified of the updated value and can perform some tasks on their own to take into account the new value.

+

Instruction

+

An Instruction is a named entity that designate something to do, an action to perform.

+

There are several types of instructions.

+

Some are internal and specific to Cockpitdecks like loading a new page of button on a deck.

+

Some other instructions are oriented towards the simulator software and designate an action to perform inside the simulation software.

+

Event

+

An Event is an object created each time somethings of interest to Cockpitdecks occurs. There mainly are two types of Events.

+

Events that come from the decks: Each time a button is pressed, an encoder is turned, a slider is slid, or a touch screen touched, a Deck Event gets generated by the deck and is sent to Cockpitdecks. Cockpitdecks analyses the event and issue the necessary Instruction(s) (see above) to handle the event.

+

Events that come from the simulation software: Each time a value changes in the simulation software, or some specific simulation event occurs, a Simulator Event is generated and sent to Cockpitdecks for handling.

+

deck-simulator.png

+

Please note that for the above objects, Data, Instruction, and Event, there always is a Cockpitdecks «internal» version of the object, and a simulator version of the object.

+

See Also

+

Glossary.

+

The Cockpit.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Deck/index.html b/Deck/index.html new file mode 100644 index 00000000..c390106e --- /dev/null +++ b/Deck/index.html @@ -0,0 +1,3028 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Deck - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

A Deck represents a deck device connected to the computer, be it a

+
    +
  • Elgato Stream Deck (several models supported),
  • +
  • Loupedeck Loupedeck Live, or
  • +
  • Berhinger X-Touch Mini,
  • +
  • A web deck represented in a web page,
  • +
+

a device that will be used to interact with the X-Plane flight simulator.

+

A Deck uses and displays a collection of buttons called a Page of buttons. A Deck can display different pages of buttons at different times; a button on a page can be assigned to load another page of buttons.

+

Each Page define the set of buttons on the deck device, what each button does when pressed or turned, and how it will appear on the deck device. The collection of pages that can be installed on a deck is called the Layout of the deck.

+

In addition to the Layout and the Pages it contains, a Deck defines deck-level attributes, such as the overall brightness of the device, or how to fill unused or undefined buttons.

+

Deck Definition

+

Decks are declared in the config.yaml file in the deckconfig folder in the decks attribute. The decks attributes contains one or more decks as defined by the following attributes:

+

Deck Attributes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDefinition
nameMandatory. Name of the deck. Must match the entry in secret.yaml file, it any.
typeMandatory. Type of deck. This points at a very precise deck brand and model. The value must match one of the deck types Cockpitdecks recognizes. The types of deck models Cockpitdecks recognizes is displayed upon startup of the Cockpitdecks application.
layoutOptional. Name of the layout for this deck. Default to name value default. See the next Section for more information.
brightnessOptional. Overall brightness of deck. Default to 100%. It might be necessary to adjust brightness at night or in low light environment.
disabledBoolean value to tell whether the deck should be enabled or not for this aircraft. Useful during development process.
default-homepage-nameOptional. Name of the page to load first in the layout. Default to index. That's why layout folder often contains a index.yaml page.
default-*Optional. Default attributes to use for deck.
+

Announce of available deck types on startup.

+
cockpit.py: loaded 19 deck types (Virtual Deck for Development, X-Touch Mini, Virtual X-Touch Mini, LoupedeckLive, virtual loupedeck.ct, virtual loupedeck.live.s, Virtual LoupedeckLive with Mosaic, Virtual LoupedeckLive, Stream Deck Original, Stream Deck Mini, Stream Deck Neo, Stream Deck +, Stream Deck XL, Streamdeck, Virtual Streamdeck Mini, Virtual Streamdeck MK.2, Virtual Stream Deck Neo, Virtual Streamdeck +, Virtual Streamdeck XL), 11 are virtual deck types
+
+

Deck Layout

+

For a given aircraft, a deck has a Layout. The Layout of a deck is the collection of Pages that will be used and displayed on the deck device for that aircraft. All these pages are grouped into a folder called a layout. A Layout is a folder in the deckconfig folder that contains pages.

+
XPlaneAircraftFolder
+  (...)
+  ⊢ deckconfig
+    ⊢ resources
+      ⊢ fonts
+      ⊢ icons
+      ⊢ ...
+    ⊢ layout1
+      ⊢ config.yaml
+      ⊢ page1.yaml
+      ⊢ page2.yaml
+      ...
+    ...
+
+

The deck definition should contain a layout attribute that indicates which layout will be used for that deck. The default layout name, if not indicated is default. If no layout is found for the deck, a default, minimal layout is created.

+

Web Decks

+

No deck? We got you covered.

+

If there is no physical deck device, Cockpitdecks can be used to replicate one of those on a web page.

+

To use an emulation of a Stream Deck device for example, it is necessary to install the streamdeck component of Cockpitdecks. When done, it is possible to create virtual Stream Deck devices and display them in a web page.

+

This is equally possible with other brands like Loupedeck or Behringer.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Adding New Deck Models/index.html b/Extending/Adding New Deck Models/index.html new file mode 100644 index 00000000..cde85c7b --- /dev/null +++ b/Extending/Adding New Deck Models/index.html @@ -0,0 +1,3119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Adding new physical deck models - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Adding a new deck model can be very simple or very difficult, depending on the deck's capabilities and software already available to access it (through the python language).

+

Cockpitdecks does its best at isolating deck specifics into

+
    +
  • deck definition files (called a Deck Type)
  • +
  • deck (device) drivers classes
  • +
+

Deck Definition File

+

Deck Abstraction and Modeling

+

Decks, in general, have been defined as devices containing buttons that can be pressed, dials, or encoders that can be turned, cursors, or sliders that can be slid, or even touch screens that can be touched or swiped.

+

These buttons can be illuminated by one or more monochrome or color LED(s), or can sometimes even display an image on a small LCD. Some can vibrate, or even produce sound.

+

Each of these interaction has led to the definition of standardized behavior. On one side, the deck expresses what is is capable of, on the other side, the Cockpitdecks designer tells which capabilities she or he wants to produce a result.

+

Please refer to the Deck Internals document to learn about how to express deck specifics in a Deck Type definition file.

+

Definition Files

+

A small file defines the deck layout and capabilities, what is available through it:

+
# This is the description of a deck's capabilities for a Elgato Streamdeck Plus device
+#
+---
+type: Streamdeck Plus
+brand: Streamdeck
+model: Plus
+driver: streamdeck
+buttons:
+  - name: 0
+    action: push
+    feedback: image
+    image: [96, 96, 0, 0]
+    repeat: 8
+  - name: 0
+    prefix: e
+    action: encoder-push
+    feedback: none
+    repeat: 4
+  # touchscreen in streamdeck package vocabulary
+  - name: touchscreen
+    action: swipe
+    feedback: image
+    image: [800, 100, 0, 0]
+
+

Through this file, Cockpitdecks is capable to determine that there are 8 (repeat) LCD buttons, named 0.. 7, capable of being pushed (action), and capable of displaying a 96x96 pixel image (feedback, image). Similarly, there are 4 encoders and a swipe screen.

+ + + + + + + + + + + + + + + + + + + + + +
AttributeDefinition
typeName the precise deck model. Referenced in config.yaml file to tell which deck is connected to the system.
driverName of the driver software inside Cockpitdecks. See below.
buttonsButton capabilities are modeled in two categories:

1. Actions, which specifies what a button is capable of,
2. View, which specifies what a button can show as a feedback to the user.
+

Button Capabilties

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDefinition
actionThe following actions (means of interaction with a deck) have been identified and are available in Cockpitdecks:

- push: ability to press a button, optionally pushing a long time,
- encoder
- press: ability to press a button, no timing information
- longpress
- touch
- swipe
feedbackSimilarly, decks defined the following feedback interactions:

- image
- led: simple on-off light
- colored-led: colored light (that can also be off)
- encoder-led: ramp of led lights for X-Touch Mini
nameName and repeat will determine the index name of the buttons.
repeatNumber of time the same button needs repeating
prefixPrefix is used to distinguish button capabilities. If a button has, for example, encoder and push capabilities, the push capabilities will use name 0 (name only), the corresponding encoder will be named e0 (prefix + name).
imageIn case of an image feedback, the image attribute sets the image size for this button, and its offset if the image is a portion of a larger display.
vibrateIf the device/button has a vibration capability
soundIf the device/button has a sound emission capability
+

Deck « Driver »

+

Deck to Computer Interaction

+

Depending on the button's action that is triggered, the deck will programmatically generate an Event. The type and content of the Event will depend on the action type.

+

push: produces an event with the identifier of the button that was pressed, and a flag indicating that the button was pressed or release.

+

encoder: will produce an Event of type encoder, with the identifier of the encoder, and a flag telling whether the encoder was turned clockwise or counter-clockwise.

+

swipe: will produce a complex swipe Event, with the position of the start of the swipe, the end of the swipe and some timing information (timestamps of start and end of swipe).

+

Technically speaking, the deck will start a thread to listen to incoming events. Interaction will be decoded (which key was pressed, when, how, etc.) and presented to Cockpitdecks as a typed Event.

+

Depending on the device driver's hardware access, events will either be presented directly to Cockpitdecks, or through a FIFO queue: Driver just enqueues events, Cockpitdecks dequeues events and does the work.

+

Computer to Deck Interaction

+

Depending on the deck's view capabilities, the computer will send the appropriate information to the deck to produce the feedback: Send an image, turn a LED on or off, with the appropriate color, emit a sound or vibrate.

+

This is performed directly through the deck's device driver, by calling the appropriate function. Most of the time, this call is direct.

+

Correspondance

+

When creating an Activation, the activation will specify which action it requires. For example, an activation that requires an encoder dial to work will require the encoder or encoder-push capability.

+

Similarly, a Representation will specify which feedback it requires. A representation that displays an image (icon, drawing, animation…) will require a image feedback for instance.

+

See Also

+

Deck Internals

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Adding a Web Deck/index.html b/Extending/Adding a Web Deck/index.html new file mode 100644 index 00000000..e555eb5e --- /dev/null +++ b/Extending/Adding a Web Deck/index.html @@ -0,0 +1,2901 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Adding a Web Deck - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Adding an Existing Web Deck

+

Creating a New Web Deck

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/API/activation/index.html b/Extending/Development/API/activation/index.html new file mode 100644 index 00000000..894fcdb2 --- /dev/null +++ b/Extending/Development/API/activation/index.html @@ -0,0 +1,2833 @@ + + + + + + + + + + + + + + + + + + + + + + + Activation - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Activation

+

Entities

+

::: cockpitdecks.buttons.activation.activation

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/API/deck/index.html b/Extending/Development/API/deck/index.html new file mode 100644 index 00000000..e94bf7fc --- /dev/null +++ b/Extending/Development/API/deck/index.html @@ -0,0 +1,2835 @@ + + + + + + + + + + + + + + + + + + + + + + + Deck - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Deck

+

Entities

+

::: cockpitdecks.deck
+ options:
+ show_inheritance_diagram: true

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/API/index.html b/Extending/Development/API/index.html new file mode 100644 index 00000000..2dc10546 --- /dev/null +++ b/Extending/Development/API/index.html @@ -0,0 +1,2897 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Application Programming Interfaces - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ +
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/API/instruction/index.html b/Extending/Development/API/instruction/index.html new file mode 100644 index 00000000..fa763520 --- /dev/null +++ b/Extending/Development/API/instruction/index.html @@ -0,0 +1,2833 @@ + + + + + + + + + + + + + + + + + + + + + + + Instruction - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Instruction

+

Entities

+

::: cockpitdecks.instruction

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/API/representation/index.html b/Extending/Development/API/representation/index.html new file mode 100644 index 00000000..833b4744 --- /dev/null +++ b/Extending/Development/API/representation/index.html @@ -0,0 +1,2833 @@ + + + + + + + + + + + + + + + + + + + + + + + Representation - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Representation

+

Entities

+

::: cockpitdecks.buttons.representation.representation

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/API/resources/decktype/index.html b/Extending/Development/API/resources/decktype/index.html new file mode 100644 index 00000000..518124aa --- /dev/null +++ b/Extending/Development/API/resources/decktype/index.html @@ -0,0 +1,2833 @@ + + + + + + + + + + + + + + + + + + + + + + + Deck Type - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Deck Type

+

Entities > Resources

+

::: cockpitdecks.decks.resources.decktype

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/API/resources/index.html b/Extending/Development/API/resources/index.html new file mode 100644 index 00000000..623a209f --- /dev/null +++ b/Extending/Development/API/resources/index.html @@ -0,0 +1,2835 @@ + + + + + + + + + + + + + + + + + + + + + + + Cockpitdecks API - Resources - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Cockpitdecks API - Resources

+

Entities

+ + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/API/simulator/index.html b/Extending/Development/API/simulator/index.html new file mode 100644 index 00000000..ebf7e0d8 --- /dev/null +++ b/Extending/Development/API/simulator/index.html @@ -0,0 +1,2833 @@ + + + + + + + + + + + + + + + + + + + + + + + Simulator - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Simulator

+

Entities

+

::: cockpitdecks.simulator

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Adding Activations/index.html b/Extending/Development/Adding Activations/index.html new file mode 100644 index 00000000..473f7c83 --- /dev/null +++ b/Extending/Development/Adding Activations/index.html @@ -0,0 +1,2960 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Adding activations - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

To add a new activation to Cockpitdecks, the developer has to create a new Activation sub-class derived from cockpitdecks.buttons.activation.Activation (or one of its subclass).

+
class SpecialAction(Activation):
+
+    ACTIVATION_NAME = "special-action"
+    REQUIRED_DECK_ACTIONS = [DECK_ACTIONS.PRESS, DECK_ACTIONS.LONGPRESS, DECK_ACTIONS.PUSH]
+
+    def __init__(self, config: dict, button: "Button"):
+
+

In the button definition:

+
index: 42
+name: ULTIMATE
+type: special-action
+
+

Example: Adding an Activation to Dim Deck Backlight

+

The following python script adds a simple activation to adjust

+
import logging
+from cockpitdecks import DECK_ACTIONS
+from .activation import UpDown
+
+logger = logging.getLogger(__name__)
+# logger.setLevel(logging.DEBUG)
+
+class LightDimmer(UpDown):
+    """Customized class to dim deck back lights according to up-down switch value"""
+
+    ACTIVATION_NAME = "dimmer"
+    REQUIRED_DECK_ACTIONS = [DECK_ACTIONS.PRESS, DECK_ACTIONS.LONGPRESS, DECK_ACTIONS.PUSH]
+
+    def __init__(self, config: dict, button: "Button"):
+        UpDown.__init__(self, config=config, button=button)
+        self.dimmer = config.get("dimmer", [10, 90])
+
+    def activate(self, event):
+        currval = self.stop_current_value
+        if currval is not None and 0 <= currval < len(self.dimmer):
+            self.button.deck.set_brightness(self.dimmer[currval])
+        super().activate(event)
+
+

Its accompanying button definition:

+
  - index: 1
+    name: ANNUNCIATOR LIGHTS
+    label: ANN LT
+    type: dimmer
+    stops: 3
+    dimmer: [100, 80, 30]
+    switch:
+      switch-style: rect
+      button-fill-color: black
+      button-underline-width: 4
+      button-underline-color: coral
+      tick-labels:
+        - "TEST"
+        - "BRT"
+        - "DIM"
+      tick-space: 10
+      tick-label-size: 30
+      tick-label-font: DIN Bold
+    options: 3way,invert,hexa
+    dataref: AirbusFBW/AnnunMode
+    set-dataref: AirbusFBW/AnnunMode
+
+

When switch is moved to TEST, backlight is set to 100%, BTR sets it to 80%, and DIM to 30%.

+

Activation API

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Adding Instruction/index.html b/Extending/Development/Adding Instruction/index.html new file mode 100644 index 00000000..a08b6805 --- /dev/null +++ b/Extending/Development/Adding Instruction/index.html @@ -0,0 +1,2878 @@ + + + + + + + + + + + + + + + + + + + + + + + Adding Instruction - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

To add a new instruction to Cockpitdecks, the developer has to create a new ButtonInstruction sub-class derived from cockpitdecks.ButtonInstruction (or one of its subclass).

+
from cockpitdecks.button import Button, ButtonInstruction
+
+
+class SpecialInstruction(ButtonInstruction):
+
+	INSTRUCTION_NAME = "button-special"
+
+	def __init__(self, button: Button, config: dict):
+		self._config = config
+
+

In the button definition:

+
index: 42
+name: ULTIMATE
+type: push
+command: button-special
+
+

Example: Adding a ButtonInstruction to Print Information

+

The following python script adds a simple instruction to print some information

+
import logging
+from cockpitdecks.button import Button, ButtonInstruction
+
+logger = logging.getLogger(__name__)
+# logger.setLevel(logging.DEBUG)
+
+class PrintInstruction(ButtonInstruction):
+    """Custom instruction to print a message"""
+
+    INSTRUCTION_NAME = "button-print"
+
+    def __init__(self, name: str, button: Button, config: dict) -> None:
+        ButtonInstruction.__init__(self, name=name, button=button)
+        self._config = config
+        self.message = config.get("message", "Hello, world!")
+
+    def _execute(self):
+        logger.info(f"{self.message}")
+
+

Its accompanying button definition:

+
  - index: 1
+    name: SAY HELLO
+    label: SAY HELLO
+    type: push
+    command: button-print
+    message: X-Plane rocks
+
+

When button is pressed, the message X-Plane rocks is printed on the console.

+

Instruction API

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Adding Representations/index.html b/Extending/Development/Adding Representations/index.html new file mode 100644 index 00000000..c24cac80 --- /dev/null +++ b/Extending/Development/Adding Representations/index.html @@ -0,0 +1,2939 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Adding representations - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Core Button

+

The core Button class is designed to isolate deck key specifics in two attribute classes:

+
    +
  1. Activation, that represents the user's interaction with the deck, and
  2. +
  3. Representation, that expresses what the deck communicates back to the user.
  4. +
+

In addition to these two attributes, the core Button class holds, keeps and maintains a set of other global, generic attributes made available to the two governing attributes.

+
    +
  • All datarefs accessed by the button,
  • +
  • Guard, if any,
  • +
  • Whether the button is "managed" and displays alternate representation,
  • +
+

Last but not least, the button keeps a copy of its entire "definition", a dictionary of name, value pairs supplied through the configuration file of the Page. Again, some attributes are mandatory and/or imposed by Cockpitdecks, like

+
    +
  • type
  • +
  • index
  • +
  • options
  • +
  • set-dataref
  • +
  • +
+

Other attributes may already be defined by existing activations and representations, but any arbitrary name, value pair can be passed and stored in the button configuration dictionary and used by its activation and/or its representation.

+

New Representation

+

Creating a new representation, like sending some special information or custom image to a LCD key is more probable.

+

To add a new representation to Cockpitdecks, the developer has to create a new Representation sub-class and derived it from cockpitdecks.buttons.representation.Representation or one of its subclass.

+
class SpecialtyIcon(Representation):
+
+    REPRESENTATION_NAME = "specialty-icon"
+    REQUIRED_DECK_FEEDBACKS = DECK_FEEDBACK.IMAGE
+
+    def __init__(self, config: dict, button: "Button"):
+        Representation.__init__(self, config=config, button=button)
+
+        self.param1_value = config.get("param1")
+
+

In the button definition:

+
index: 42
+name: SPECIAL_ICON
+type: push
+specialty-icon:
+	param1: value
+
+

Representation API

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Adding a Simulator/index.html b/Extending/Development/Adding a Simulator/index.html new file mode 100644 index 00000000..12ce9346 --- /dev/null +++ b/Extending/Development/Adding a Simulator/index.html @@ -0,0 +1,2905 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Adding a simulator - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

To add a new simulator to Cockpitdecks, the developer has to create a new Simulator sub-class derived from cockpitdecks.Simulator (or one of its subclass).

+
class SubLogicFlightSimulator(Simulator):
+
+    name = "A2FS1"
+
+    def __init__(self, cockpit, environ):
+
+

Simulator API

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Button Activations for Developers/index.html b/Extending/Development/Button Activations for Developers/index.html new file mode 100644 index 00000000..32b15ce7 --- /dev/null +++ b/Extending/Development/Button Activations for Developers/index.html @@ -0,0 +1,2975 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Activation for developers - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Cockpitdecks offers a few special activations that are normally not called in regular use.

+

Reload

+

type: reload

+

No option. Provoke the reload of all decks from initialisation.

+

The procedure first gracefull terminates the current setup, and then reloads the setup.

+

If a page different than the home page was currently loaded, the process will try to reload the same page if available in the new setup.

+

This activation is mainly used during development process to create buttons, alter their appearance and re-load the configuration to see the changes.

+

(Please note that reloading decks is a complex process, since it involves the reset of all devices, reloading all configurations, and displaying pages as they used to be, if still present.)

+

Stop

+

type: stop

+

No option. Gracefully stops all decks and terminates Cockpitdecks.

+

Inspect

+

type: inspect

+

Provoke the output of information for all buttons of all pages or all decks.

+

Mainly for development purpose.

+

Inspection always starts at the Cockpit level and may or may not crawl down into its constituting parts like decks, layouts, pages, and buttons. Some inspection terminates earlier in the drill down. For example, threads inspection stops at the Deck level.

+

Attribute

+

Button Inspect has one attribute

+

what

+

The value of the what attribute determine what is displayed when activated.

+

what Attribute Values

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueDescription
threadslist Cockpitdecks and deck threads that are currently running.
datarefslist datarefs and values (the page containing the buttons need to be loaded first before it can display values).
datarefs-listenerList all datarefs and which buttons (listeners) are using the dataref.
statuslist internal variables and statuses of each button.
validcheck button validity and report invalid status.
descprint a description of what each button does in plain English, both for activation and representation.
imageproduces an image of each deck, images are saved in the Cockpitdecks home directory and named after the deck.
longpresslist Button Activation#ShortOrLongpress command that should have a couple of additional commands added to X-Plane through the plugin.
+
index: 4
+type: inspect
+what: desc
+
+

When button index 4 is pressed, it will display what each button does (description) in plain English on the output or debugging screen or file.

+

Notes for Button Designers

+

Button Instantiation

+

When a button is created, internal meta data are set first. Second, the Activation is installed and initialised. Third, the Representation is installed and initialized, as it may already use some activation information for rendering. Finally, the button is initialised. It will be rendered when the page that contains it is loaded on a deck.

+

Button Validity

+

Each button has a validity function that ensures that all necessary attributes are provided in its definition. If the activation of the button is not valid, its activation function will never be triggered, because of missing or misconfigurated parameters. If its representation is not valid, it will not be rendered on the deck.

+

If a button is not valid, a small red triangle appears in the lower right corner of the key icon if the button is capable of representing it. A small blue triangle appears in the lower right corner of the key icon if Cockpitdecks suspect the button is a placeholder.

+

Button Description and Inspection

+

Each button has an describe() method that prints in plain English what the button does and what it renders on the deck.

+

Each button has an inspect(what: str) method that exposes internal values and state. The inspect method takes one parameter what that determines what is displayed when invoked.

+

These methods can be invoked from the Inspect button activation.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Activation/index.html b/Extending/Development/Internals/Activation/index.html new file mode 100644 index 00000000..fe0ed656 --- /dev/null +++ b/Extending/Development/Internals/Activation/index.html @@ -0,0 +1,2858 @@ + + + + + + + + + + + + + + + + + + + + + + + Activation - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Activation

+ +

Activation occurs when a Button is requested to handle an Event.

+

A first step consists of the Activation preparation and initialization. This occurs when the deck is installed and each page created.

+

Initialization of the Activation will result in the creation of one or more Instructions. In the process, the Performer entity responsible for executing the Instruction is clearly identified. For example, CockpitInstruction are performed by the Cockpit entity, while SimulatorInstructions are executed by the simulator software.

+

The initialization of the Activation result in a global status is_valid() that returns True if the Activation contains all information necessary for handling events.

+

When the Button receives the event, it calls the activate() function on its activation, supplying the event.

+
	# In the Button class
+	def activate(self, event) -> bool:
+		result = self._activation.activate(event)
+		if result:
+			self.render()
+		else:
+			logger.warning("there was an issue handling the event")
+
+

Activate() functions return the status of the execution, True if all instructions were carried out without error.

+

The Activation activate() function always proceeds in similar patterns:

+
    +
  1. It first checks whether the Activation is capable of handling the event.
  2. +
  3. Depending on the event type, it executes one or more Instructions, collecting the result of the execution.
  4. +
  5. When all Instructions have completed, it returns a global status for the entire Activation.
  6. +
+

Here is a pseudo-code that handles button press events:

+
	# In the Activation class
+    def activate(self, event: PushEvent) -> bool:
+	    status = False
+        if not self.can_handle(event):
+            return False
+        if not super().activate(event):
+            return False
+        if event.pressed:
+            status = self.instruction.execute()
+        return status  # Normal termination
+
+

From then on, the Button must decide what to do next, including if necessary regenerating its appearance.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Cockpitdecks Internals/index.html b/Extending/Development/Internals/Cockpitdecks Internals/index.html new file mode 100644 index 00000000..6d0791cc --- /dev/null +++ b/Extending/Development/Internals/Cockpitdecks Internals/index.html @@ -0,0 +1,3041 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Cockpitdecks Internals - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

As simple as it may appears when working, Cockpitdecks is a complex piece of software that relies on numerous technologies, systems, and interfaces to provide, ultimately, a confortable user experience.

+

First of all, Cockpitdecks tries to provide a uniform representation of different deck models. Each deck model, from different manufacturers, has its own way of doing things. Different decks are accessed differently, some through basic serial (USB) interfaces, some through application programming interfaces, and some other through existing "protocols" made to talk to devices like HID or MIDI. Some device even allow several methods to be used.

+

Cockpitdecks uses the appropriate method to hide the complexity of accessing the deck devices, to hide their particularities, at the expense of a complex and modular installation process. Some will use a single device, some other will use more than one, combining different models and brands to suit their needs.

+

Cockpitdecks communicates with X-Plane through the network UDP protocol. This offers the advantage that Cockpitdecks and X-Plane do not necessarily need to run on the same computer, as long as both computer are on the same local network.

+

Through UDP ports, X-Plane reports some internal parameter values (called datarefs), and accepts commands to execute.

+

Never ever forget that in the UDP protocol, there is no guarantee of delivery, ordering, or duplicate protection. This is inherent to the UDP protocol. When something is sent, it is never guaranteed that it will be received or acknowledged.

+

Architecture

+

Cockpitdecks Software Entities

+

entities.svg

+

Cockpitdecks proceeds by starting autonomous threads of execution that monitor different aspects of the interaction of decks with X-Plane.

+

Cockpitdecks threads are often created in start() procedures and terminated in terminate() procedures. Communication with the thread is performed through synchronous Queues.

+

In addition, each deck has its own, internal, mechanism to capture user interactions.

+

Execution of Actions

+

As today, things that occurs on a deck are captured by a lower level computer software module. In the case of Cockpitdecks and its Streamdeck, Loupedeck and Berhinger devices, each of those lower level software module is designed to use a user-provided callback function that is called each time something occurs on the deck. That's how event enters Cockpitdecks.

+

During initialization of a deck, Cockpitdecks installs a small, minimalist callback function in the deck software module. This callback function processes data provided from the deck and immediately converts it into an Event (a Deck Event) that gets enqueued right away for later handling. This process is optimized to be as minimal and as fast as possible. The Queue where events are then enqueued is called the Event Queue.

+

The Event Queue that receives all events from all decks connected to the system is unique for a Cockpit. It is the entry point for all interactions into Cockpitdecks. It behaves like a clean separator between lower level interaction handling at the device and Cockpitdecks. The callback function is responsible for decoding the information received from the device and crafting a typed Event that correspond to the interaction that occurred. The event also carries the necessary complementary information and data like, for instance, the precise button that was pressed or turned.

+

activations.svg

+

Inside Cockpitdecks' Cockpit, a thread of execution receives the event from the Event Queue and immediately executes them to perform the action they carry.

+

The action is executed in a separate thread of execution from those of the lower level physical interactions. Should execution of the action fail, the thread of execution of the capture is not affected.

+

Dataref Monitoring

+

There is a similar mechanism for dataref values capture and processing.

+

In the Simulator entity, a thread monitors datarefs by collecting them as they arrive on UDP port. It compare each value with the last one captured and enqueue a "value changed" event into the Event Queue if it was updated.

+

dataref updates.svg

+

Each dataref maintains a list of buttons that use/rely on it, and each of those buttons gets notified of the change to adjust. When a button receives the message that one of its dataref has changed, it can adjust its internal state, and if it is currently rendered on a deck, adjust its display.

+

Thread for Connection to X-Plane

+

In the Simulator, a thread permanently monitors the connection of Cockpitdecks to X-Plane. When there is no connection, the thread attempt to initiate a new connection until it succeeds.

+

When a new connection is created, it immediately request dataref updates and update all decks with all dataref values.

+

If the connection breaks, it restarts its attempts to connect.

+

When there is no connection to X-Plane, Cockpitdecks works as expected, however, no command get issued to X-Plane, and no dataref value gets collected, hence, no deck icon gets updated to reflect the state changes.

+

Internal Datarefs

+

Datarefs whose name starts with a prefix (currently data:) behave like any other datarefs but are neither forwarded to X-Plane, nor read from it. They can be set, read, etc. like any other datarefs allowing for a kind of inter-button communication: one button sets it, another one adjust its appearance based on it, even buttons on another deck!

+

Truly, the sky is the limit. Enjoy.

+

Internal Resource Folder

+

In addition to aircraft specific definitions, Cockpitdecks contains in its core, a default configuration used as a fall back if no value is found at the aircraft specific level. These global core configuration is found in a resources folder inside Cockpitdecks software package. This folder should never be changed since it affects the entire Cockpitdecks application. It contains the following files and subfolders:

+

config.yaml

+

This is a global level configuration file. It always is loaded first and can be overwritten by aircraft, deck, or page-specific variants.

+

icons

+

Icons in this folder are available to all aircrafts.

+

fonts

+

Fonts in this folder are available to all aircrafts.

+

Cockpitdecks provides a few fonts found here and there together with their respective copyright files.

+

docs

+

A copy of Cockpitdecks documentation is included there. The documentation folder produced in the GitHub wiki of Cockpitdecks.

+

Image files

+

The resource folder contains a few image files used as logos and wallpapers.

+

There is also an image with color names that can be used in color attributes.

+

iconfonts.py

+

This file defines icon fonts. Icon fonts are fonts that are used to display iconic characters often named intuitively. Cockpitdecks comes with a copy of Font Awesome icons, and Weather Icons.

+

constants.py

+

Defines a few constants that should never be changed. Change at your own risk.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Configuration Files and Attributes/index.html b/Extending/Development/Internals/Configuration Files and Attributes/index.html new file mode 100644 index 00000000..ae281a96 --- /dev/null +++ b/Extending/Development/Internals/Configuration Files and Attributes/index.html @@ -0,0 +1,2939 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Configuration Files and Attributes - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Configuration Files and Attributes

+ +

Config files are kept in a Config structure which allows for easy access to attribute values.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeContent
cockpit._configdeckconfig/config.yaml file
cockpit._resources_configresources/config.yaml global file
deck._configportion of the deckconfig/config.yaml file for this deck
deck.deck_typeDeck Type file for this deck
deck._layout_configlayout/config.yaml file
decktype._configdecktype.yalm file
page._configpage.yaml file
button._configportion of the buttons attribute above for this button
button._defportion of the decktype buttons for this button
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Data and Value Changes/index.html b/Extending/Development/Internals/Data and Value Changes/index.html new file mode 100644 index 00000000..13dc2276 --- /dev/null +++ b/Extending/Development/Internals/Data and Value Changes/index.html @@ -0,0 +1,2833 @@ + + + + + + + + + + + + + + + + + + + + + + + Data and Value Changes - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Data and Value Changes

+ +

Cockpitdecks uses two types of value that can come from different sources:

+
    +
  1. Data are simple scalar typed values (mainly string, float or integer). They can either come from the simulator software or Cockpitdecks internal values.
  2. +
  3. Value are complex scalar typed values that depends on one or more Data. If the Value depends on more than one Data, a Formula is used to combined all Data together and ultimately provide a single final value.
  4. +
+

Values are used by buttons and observables. Values are used when there is a need to combine and modify raw values as provided by the simulator or by Cockpitdecks.

+

Values are notified when one of its underlying Data has changed. This allows the Value to compute its new value. the Value in turn notifies the Button or the Observable that created it of its change. The Button or Observable can adjust as needed.

+

Changes of Data value enter Cockpitdecks through Events. The SimulatorEvent (or one of its sub-class) is used to report the change of a value in the simulator software. The CockpitEvent (or one of its sub-class) to report change of value in the Cockpit.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Dataref Scanning/index.html b/Extending/Development/Internals/Dataref Scanning/index.html new file mode 100644 index 00000000..f3f772a0 --- /dev/null +++ b/Extending/Development/Internals/Dataref Scanning/index.html @@ -0,0 +1,3150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Simulator Data Collection - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Button definitions contains several attributes where datarefs can be used. Cockpitdecks needs to parse those attributes to create a list of datarefs to monitor.

+

This page is an attempt to specify formally where datarefs are to be found.

+

Button-Level

+

Button Value

+

Formula

+

Single dataref

+

Multiple Datarefs

+

(Dataref list, or dictionary.)

+

Single Dataref with Multiple Value (Array)

+

(List of values)

+

Multiple Datarefs

+

(«Dictionary» of values)

+

Annunciator Specific

+

Guard

+

«Managed» Mode

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Deck Event Processing/index.html b/Extending/Development/Internals/Deck Event Processing/index.html new file mode 100644 index 00000000..4b9f0eb4 --- /dev/null +++ b/Extending/Development/Internals/Deck Event Processing/index.html @@ -0,0 +1,2943 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Deck Event Processing - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

From the parameter supplied in the callback function, Cockpitdecks determine the type of interaction that occurred (pushed, turned, swiped…). For that interaction, an Event of a precise type is created, with all detailed parameters available to it. The callback function does not execute the activation but rather enqueues the event for later processing.

+

In Cockpitdecks, another thread of execution reads events from the queue and perform the required action. This cleanly separate event collection and event "execution" in two separate process threads.

+

Activation

+

The activation is the piece of code that will process the event.

+
class Push(Activation):
+    """
+    Defines a Push activation.
+    The supplied command is executed each time a button is pressed.
+    """
+    ACTIVATION_NAME = "push"
+    REQUIRED_DECK_ACTIONS = DECK_ACTIONS.PUSH
+
+

Activation usually leads to either

+
    +
  • one or more command sent to the simulator for execution
  • +
  • internal changes of the deck, like loading a new page of buttons
  • +
  • or both
  • +
+

Representation

+
class Annunciator(DrawBase):
+    """
+    All annunciators are based on this class.
+    See docs for subtypes and models. 
+    """
+    REPRESENTATION_NAME = "annunciator"
+    def __init__(self, config: dict, button: "Button"):
+        self.button = button
+
+

Similarly, when a Representation code is created, it must mention its identification keyword REPRESENTATION_NAME that will be searched in the button definition attribute.

+

The REQUIRED_DECK_FEEDBACKS determine which of the deck's definition feedback type is requested to be able to use the Representation().

+

Button Definition

+

The ACTIVATION_NAME is the string that the button definition must use to trigger that activation (type attribute):

+
  - index: 1
+    name: MASTER CAUTION
+    type: push
+    command: sim/annunciator/clear_master_caution
+    annunciator:
+      text: "MASTER\nCAUT"
+      text-color: darkorange
+      text-font: DIN Condensed Black.otf
+      text-size: 72
+      dataref: AirbusFBW/MasterCaut
+    vibrate: RUMBLE5
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Deck Internals/index.html b/Extending/Development/Internals/Deck Internals/index.html new file mode 100644 index 00000000..ab5b8db9 --- /dev/null +++ b/Extending/Development/Internals/Deck Internals/index.html @@ -0,0 +1,3295 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Deck Internals - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Deck Internals explains how Cockpitdecks discovers about miscellaneous deck hardware capabilities and how user interactions on a physical deck device enter Cockpitdecks.

+

How Deck User Interactions Enter Cockpitdecks

+

When a user want to use a deck with Cockpitdecks, it is necessary to have a python package available to interact with it. This python package is not provided by Cockpitdecks but by other developers who bridged the physical deck hardware with the python language.

+

By design and coïncidence, all three deck brands currently used by Cockpitdecks (Elgato, Loupedeck, and Berhinger) proceed with a similar mechanism: The python package that interfaces the physical deck to the python language request to supply a callback function. That function is called each time an interaction occurs on the physical deck device.

+

When Cockpitdecks is started, its scans for available devices, checks whether the interfacing software package is available, and if it is, installs its callback function into the python package for that deck.

+

From that moment on, each time something occurs on the physical deck device, Cockpitdecks' callback function gets called. In that callback function, Cockpitdecks tries to spend a minimum time. From the data it receives from the interfacing python package, it creates an Event with all necessary data and enqueues it in Cockpitdecks for later processing. The Event the callback function creates is aptly called a Deck Event.

+

The Deck Event contains information about the deck, of course, but also the precise button, knob, encoder, slider, screen… that was manipulated and the type of interaction that occurred (pushed, turned, swiped…) All that information is in the Deck Event and is sent to Cockpitdecks.

+

That's how physical deck interaction enters Cockpitdecks.

+

Cockpitdecks processes events that enter its queue. Cockpitdecks instruct the event to run. That's how and when actions are actually performed, like sending a command to the simulator or changing the value of a dataref.

+

Deck Type

+

Cockpitdecks discovers about deck capabilities through a Deck Type structure.

+

A deck is presented to Cockpitdecks through a deck definition file called a Deck Type. The deck definition file describes the deck capabilities:

+
    +
  • How many buttons and how they can be manipulated,
  • +
  • How many dials, if they can be turned, or pushed
  • +
  • Feedback LCD screens for icons
  • +
  • Feedback LED, optionally colored
  • +
  • Ability to emit vibration or sound
  • +
  • etc.
  • +
+

Deck Definition

+

Here is for example, a deck configuration file for a Loupedeck LoupedeckLive device.

+
# This is the description of a deck's capabilities for a Loupedeck LoupedeckLive device
+#
+---
+name: LoupedeckLive
+driver: loupedeck
+buttons:
+  - name: 0
+    action: push
+    feedback: image
+    image: [90, 90, 0, 0]
+    repeat: 12
+  - name: left
+    action: swipe
+    feedback: image
+    image: [60, 270, 0, 0]
+  - name: right
+    action: swipe
+    feedback: image
+    image: [60, 270, 420, 0]
+  - name: 0
+    prefix: e
+    action: [encoder, push]
+    feedback: none
+    repeat: 6
+  - name: 0
+    prefix: b
+    action: push
+    feedback: colored-led
+    repeat: 8
+  - name: buzzer
+    action: none
+    feedback: vibrate
+
+

Attributes

+ + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDefinition
nameName used inside Cockpitdecks to identifying the deck model.
driverKeyword identifying the deck software driver class. (See main drivers class above.)
backgroundThe background attribute is an optional attribute only used by web decks. It specifies a background color and/or image to use for web deck representation. See explanation and exemple below.
buttonsThe Buttons contains a list of Deck Button Type Block descriptions.

This attribute is named Buttons, with Button having the same meaning as in Cockpitdecks. A Deck Type Button is a generic term for all possible means of interaction on the deck:

1. Keys to press,
2. Encoders to turn,
3. Touchscreens to tap or swipe
4. Cursors to slide

A list of button types, each ButtonType leading to one or more individual buttons identified by their index, built from the prefix, repeat, and name attribute. See below.
+

Deck Type Button Block

+

A Deck Type Button Block defines either a single button, or a group of identical buttons. For example, if a deck has a special, unique, «Escape» button, it can be defined alone in a Deck Type Button Block. Similarly, if a deck consist of a grid of regularly spaced 6 by 4 keys that are all the same, they can also be defined in a single Deck Type Button Block.

+

Attributes

+ + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDefintion
nameName of the button type.

The name of the button type is

- either the final name of the button, like touchscreen, when there is a single button with that name on the deck,
- or an integer value that will be used to build the button names, in the case the block defines a sets of identical buttons.
actionsInteraction with the button. Interaction can be:

- none: There is no interaction with the button. It is only used for display purpose. (none interaction can be omitted.)
- press: Simple press button that only reports when it is pressed (one event)
- push: Press button that reports 2 events, when it is pushed, and when it is released. This allow for "long press" events.
- swipe: A surface swipe event, with a starting touch and a raise events.
- encoder: A rotating encoder, that can turn both clockwise and counter-clockwise
- cursor: A linear cursor (straight or circular) delivering values in a finite range.

Action can ba a single interaction or an array of interactions like [encoder, push] if a button combines both ways of interacting with it.
feedbacksFeedback ability of the button. Feedback can be:

- none: No feedback on device, or direct feedback provided by some marks on the deck device. (none feedback can be omitted.)
- image: Small LCD iconic image.
- led: Simple On/Off LED light.
- colored-led: A single LED that can be colored.
- multi-leds: Several, single color, LED on a ramp.
- encoder-leds: Special encoder led ramp for X-Touch Mini (4 modes)
- vibrate: emit a buzzer sound.
repeatIn case of a set if identical buttons, repeat if the number of time the same button is replicated along width (x) and height (y) axis.

If only one value is supplied, it is supposed to be [value, 1] array. For vertical layout, specify [1, value] instead.
+
Examples of button names
+

In the case of a single button, the name of the button will be touchscreen and that name needs to be unique for the deck type.

+
name: touchscreen
+
+

In the case of a set of identical buttons, the name of the button will be built from other attributes:

+
name: 5
+prefix: k
+repeat: [4, 3]
+
+

Names of buttons will be: k5, k6, k7, … k16.

+
Feedback Trick
+
+

Trick

+

If a deck has a vibrate capability, it is advisable to declare it as a separate button of interaction, and use that button like any other. Vibrate is a feedback mechanism.

+
+

Deck Type Button Block: Additional Attributes for Web Decks

+

The above Deck Type Button Block attributes are necessary for all decks, both physical and web decks. Web Decks also contain an additional series of attributes that drive the drawing of the deck in a web navigator window.

+
+

Web Deck Drawings

+

For simplicity, Web Deck Drivers are drawn on an HTML Canvas, which is a pixel-driven drawing space. Web Decks are drawn with images and drawing instructions that use the pixel as a unit for display.

+
+

Web decks can have the following types of interactive buttons:

+
    +
  1. Keys (simple press, long press, etc.)
  2. +
  3. Encoders (turned clockwise, counter clockwise)
  4. +
  5. Touchscreen (pressed, swiped)
  6. +
  7. Slider (slid between 2 range values)
  8. +
+

When rendered in a browser window, web deck interaction means are materialised through a changing pointer cursor (arrow, curved arrow (encode), single dot (push), double dot (pull), etc.)

+
+

Background Deck Type Attribute

+

Please read above in this page the background Deck Type attribute used to specify a background image to use for web deck display.

+
+

Attributes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDefinition
dimensionThe dimension attribute can be a single integer value or a list of two values.

It determine the size of the button has drawn on the Web deck.

If the feedback visualisation is an image, the image attribute specifies the characteristics of the image. Xis horizontal and correspond to the width, Y is vertical and correspond to the height.
layoutLayout of the buttons on the web deck canvas.
- Offset
- Spacing
Buttons will be arranged at regular interval, starting from Offset, with supplied spacing between the buttons. Button sizes are specified in the Dimension attribute.
hardwareConfiguration information for a specific drawing representation of this hardware button.
empty-buttonMinimal Button definition to create an empty, dummy button. This dummy button will becreated and used to generate an "empty" hardware representation (completely off). (In other words, if a button with hardware representation is not defined, this definition will be used to create a button.) See below.
optionsComma-separated list of options, a single option can either be a name=value, or just a name, in which case the value is assumed to be True.

options: count=8,active

sets options countto value 8, and active to True. active is equivalent to active=true.
+

Deck Type background

+

In addition to the above button definitions, a deck type may contain a background attribute. This attribute is only used by web decks. The background attribute defines the background of the web page where the web deck will be rendered. The background can either be

+
    +
  1. A PNG image,
  2. +
  3. A solid color and size information.
    +In the case of a PNG image, the size is deduced from the size of the image.
  4. +
+

Attributes

+ + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDefinition
imageName of a PNG image, with extension. No default.
colorColor of the background of the web page. No default.
sizeArray of two values with width and height of the web canvas. Defaults to (200, 100) pixels.
overlayStatic text or image overlay. Not implemented yet.
+

Special Button « Hardware » Representation

+

Some deck buttons need a special representation or drawing to visually reproduce the physical deck equivalent button. Examples of such special representations are

+
    +
  • LoupedeckLive « colored » numbered buttons,
  • +
  • X-Touch Mini LEDs around the encoders.
  • +
+

These highly specific « drawings » are performed on side representations called Hardware Representations.

+

Technically speaking, they behave very much like LCD representations:

+
    +
  • Some screen space is reserved on the canvas to host the representation,
  • +
  • The hardware representation driver produces an image that mimics the hardware button on the send,
  • +
  • Cockpitdecks « sends » the hardware representation image to the web deck for display in the reserved space,
  • +
  • Like any other representation, the hardware representation gets updated each time the underlying values gets updated.
    +Hardware representation only exists for web decks to draw a very specific button.
  • +
+

Empty Button Definition

+

Hardware Representations need to know how to represent themselves in case they are not used or defined on a page. To present nicely, Cockpitdecks needs to know how to draw an "undefined", unused Hardware Representation. To do this, Cockpitdecks uses a trick: It dynamically create an dummy placeholder button from the Empty Button Definition for its Hardware Representation. Basically, only two attributes need to be defined: A type (often set to none) and an attribute that tells its Hardware Representation. Optionally, a default, initial value can be supplied. Sometimes, some mandatory or optional Hardware Representation attributes need to be supplied as well. Here is an exemple of a simple LED representation.

+
hardware:
+	type: virtual-xtm-led
+	empty-button:
+		type: none
+		led: single
+		initial-value: 1
+
+

hardware-representation-empty.png

+

Examples of Deck Type Button Definition Block

+

Single Button Definition

+
name: Virtual Deck
+driver: virtualdeck
+buttons:
+  - name: left
+    action: [push, swipe]
+    feedback: image
+    dimension: [52, 270]
+    layout:
+      offset: [96, 78]
+    options: corner_radius=4
+
+

Multiple Button Definition

+
name: Virtual Deck
+driver: virtualdeck
+buttons:
+  - name: 0
+    prefix: e
+    repeat: [1, 3]
+    action: [encoder, push]
+    dimension: 27
+    layout:
+      offset: [45, 115]
+      spacing: [0, 41]
+  - name: 3
+    prefix: e
+    repeat: [1, 3]
+    action: [encoder, push]
+    dimension: 27
+    layout:
+      offset: [624, 115]
+      spacing: [0, 41]
+
+

Installed Deck Types

+

On startup, Cockpitdecks reports which deck types are available:

+
Cockpitdecks 12.7.2.20241209 © 2022-2024 Pierre M <pierre@devleaks.be>
+Elgato Stream Decks, Loupedeck decks, Berhinger X-Touch Mini, and web decks to X-Plane 12.1+
+
+INFO MainThread start.py:<module>:315: Initializing Cockpitdecks..
+INFO MainThread cockpit.py:add_extensions:610: loaded extensions cockpitdecks_xp, cockpitdecks_wm, cockpitdecks_ld, cockpitdecks_ext, s_sd, cockpitdecks_bx
+INFO MainThread cockpit.py:init:466: available simulators: X-Plane
+INFO MainThread cockpit.py:init:469: available deck drivers: virtualdeck, loupedeck, xtouchmini, streamdeck
+WARNING MainThread xplane.py:add_datarefs_to_monitor:1189: no connection
+INFO MainThread xplane.py:add_cockpit_datarefs:819: monitoring 1 cockpit datarefs
+WARNING MainThread xplane.py:add_datarefs_to_monitor:1189: no connection
+INFO MainThread xplane.py:add_simulator_datarefs:832: monitoring 7 simulator datarefs
+INFO MainThread cockpit.py:init_simulator:497: simulator driver XPlane 1.4.0 installed
+INFO MainThread cockpit.py:init_simulator:501: COCKPITDECKS_PATH=/Users/xplane/X-Plane 12/Aircraft/Laminar Research:/Users/xplane/X-Plane 12xtra Aircraft
+INFO MainThread cockpit.py:load_cd_fonts:1494: 18 fonts loaded, default font=D-DIN.otf, default label font=D-DIN.otf
+INFO MainThread cockpit.py:load_cd_icons:1369: 18 icons loaded from cache
+INFO MainThread cockpit.py:load_cd_sounds:1543: 8 sounds loaded
+DEBUG MainThread observable.py:init:130: observable Aircraft loaded: listening to {'sim/aircraft/view/acf_livery_path'}
+INFO MainThread cockpit.py:load_cd_observables:1284: loaded 1 observables
+INFO MainThread cockpit.py:load_deck_types:1336: loaded 19 deck types (Virtual Deck for Development, LoupedeckLive, virtual loupedeck.ct, pedeck.live.s, Virtual LoupedeckLive with Mosaic, Virtual LoupedeckLive, Stream Deck Original, Stream Deck Mini, Stream Deck Neo, Stream eam Deck XL, Streamdeck, Virtual Streamdeck Mini, Virtual Streamdeck MK.2, Virtual Stream Deck Neo, Virtual Streamdeck +, Virtual XL, X-Touch Mini, Virtual X-Touch Mini), 11 are virtual deck types
+INFO MainThread cockpit.py:scan_devices:800: device drivers installed for virtualdeck (included), loupedeck 1.4.5, xtouchmini 1.3.6, 0.9.6; scanning for decks and initializing them (this may take a few seconds)..
+INFO MainThread cockpit.py:scan_devices:825: found 1 loupedeck
+INFO MainThread cockpit.py:scan_devices:825: found 1 xtouchmini
+INFO MainThread cockpit.py:scan_devices:825: found 3 streamdeck
+INFO MainThread start.py:<module>:317: ..initialized
+
+

Deck Driver

+

A particular deck will come with software that interfaces it with the python language. That piece of software is a deck device driver. It is necessary to

+
    +
  1. Collect interactions from the device (which button has been pressed, how long?, which encoder has been turned, how fast?)
  2. +
  3. Send feedback visualisation instruction to the device to reflect the state change, instruct to draw an image, emit a sound, etc.
    +Device drivers are very specific and particular software. To further isolate their specificities, Cockpitdecks uses a Deck Driver interface, a bridge between Cockpitdecks and the device driver that controls the deck.
  4. +
+

Currently, this require the coding of a single python class derived from the cockpitdecks.deck class, with the following functions:

+

General:

+
    +
  • make_default_page
  • +
  • render
  • +
+

Interaction control:

+
    +
  • key_change_callback
  • +
+

In some drivers, there sometimes is a callback function per interaction type:

+
    +
  • key_change_call_back,
  • +
  • dial_change_callback
  • +
  • touch_callback…
  • +
+

Feedback:

+

If the deck has image capabilities:

+

See list below.

+

If the deck has sound and/or vibrating capabilities:

+
    +
  • vibrate
  • +
+

If the deck has lit button with color capabilities:

+
    +
  • set_button_color
  • +
+

If the deck has lit button without color capabilities:

+
    +
  • set_button_led
  • +
+

If the deck has lit button with several led capabilities (led ramps, etc.):

+
    +
  • set_button_encoder_led
  • +
+

The following functions are also necessary and can be overwritten if necessary.

+
    def __init__(self, name: str, config: dict, cockpit: "Cockpit", device=None)
+    def set_deck_type(self)
+
+	def init(self)
+    def get_id(self) -> str:
+
+	def is_virtual_deck(self) -> bool:
+    def get_deck_button_definition(self, idx)
+    def get_deck_type(self) -> DeckType:
+    def get_attribute(self, attribute: str, silence: bool = False)
+    def load(self)
+
+	def change_page(self, page: str | None = None)
+    def reload_page(self)
+    def set_home_page(self)
+    def load_home_page(self)
+    def make_default_page(self, b: str | None = None)
+
+	def get_button_value(self, name)
+
+	def get_index_prefix(self, index)
+    def get_index_numeric(self, index)
+    def valid_indices(self, with_icon: bool = False)
+    def valid_activations(self, index=None)
+    def valid_representations(self, index=None)
+
+	def inspect(self, what: str | None = None)
+    def print_page(self, page: Page)
+
+    def vibrate(self, button)
+    def set_brightness(self, brightness: int)
+
+	def render(self, button: Button)
+	def fill_empty(self, key)
+    def clean_empty(self, key)
+
+	def start(self)
+    def terminate(self)
+
+

For deck with iconic display capabilities:

+
    def get_image_size(self, index)
+    def create_empty_icon_for_key(self, index)
+    def get_icon_background(
+        self,
+        name: str,
+        width: int,
+        height: int,
+        texture_in,
+        color_in,
+        use_texture=True,
+        who: str = "Deck",
+    ):
+    def create_icon_for_key(self, index, colors, texture)
+    def scale_icon_for_key(self, index, image, name: str | None = None)
+    def fill_empty(self, key)
+    def clean_empty(self, key)
+    def set_key_icon(self, key, image)
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Design Helper/index.html b/Extending/Development/Internals/Design Helper/index.html new file mode 100644 index 00000000..02e661ec --- /dev/null +++ b/Extending/Development/Internals/Design Helper/index.html @@ -0,0 +1,2997 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Web Deck Designer Tools - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Cockpitdecks comes with two designer tools.

+
    +
  1. A Web Deck Designer
  2. +
  3. A Button Designer
  4. +
+

Web Deck Designer

+

The Deck Designer is a tool that help positioning, sizing, and naming elements on a web deck background image.

+

deckdesigner.png

+

On the above designer, rectangular or circular buttons, encoder, or hardware image place holders can be positioned and sized.

+

A layout can be saved and loaded later. When saving, two files are created, one for the graphical representation with the design, and one Yaml file in Deck Type format. The latter can be used as a skeleton to define a new virtual web deck.

+
+

Layout and layout.

+

In a very unfortunate choice of vocabulary, the word layout is used to designed two different things in Cockpitdecks.

+
    +
  1. Common layouts are sets of pages. A deck must reference a layout, that is a deck must reference a set of pages it will load and propose to the user. A common layout is just a folder that contains pages to be loaded on a deck.
  2. +
  3. Web Deck layout are arrangement of buttons, encoders, and hardware images on the web page of a web deck. They are quickly created, saved, and edited with the Deck Designer tool. Look at the above image, Deck Designer allows the user to add, remove, resize different elements to interact with over the background image. This arrangement of elements is also awkwardly called a layout. Sorry. Fortunately, the second meaning of layout is less frequent, and only used by Web Deck designers.
  4. +
+
+

The web deck can then be used with the same background image, and each button, encoder, or hardware image will be laid over the background at the very precise defined position and size.

+

Of course, manual tweaking of position and sizes is sometimes necessary, but the gross work of estimation is completed in fun time. For instance image size may need to be evenly rounded to the same value for all buttons for aesthetic layouts.

+

Development Example

+

See the A321 Overhead Panel example. Deck Designer is used to define the very precise position of each annunciator or switch.

+

Button Designer

+

The Button Designer is a simple tools that help test and preview button design.

+

A button can be defined, either through a form by filling a few intuitive fields, or by typing Yaml code directly in the code window. The design tool also can generate Yaml code from the form parameters values.

+
+

Form is not completed from code area

+

The opposite is not true, when entering or adjusting code in the coding window, form elements are not updated.

+
+

buttondesigner.png

+

By pressing the render button, Cockpitdecks will generate a button image and make it available for display in the Button Designer preview window. If the simulator is running, the button will show the simulated values like they would appear on the real deck.

+

When "Saving…" the button, it is added to a file in the

+
< current-aircraft | deck-name >/deckconfig/<layout-name>/<page-name>.yaml
+
+

file at the index position mentioned in the first part of the form. (If no layout or page name is supplied int he form, acceptable default values are provided.)

+

So is it possible to design buttons one by one and add them after verification to the page file.

+

Once the page is complete, it can be copied over to the deckconfig folder of the aircraft.

+

Notes

+

Selecting a new deck name or a new button index resets the forms.

+

To generate code, the code window must be empty. Pressing render while the code window is not empty has no effect.

+

Representations are added one at a time, some are not currently working.

+

What is working well, it the testing area: You can paste your button definition yaml code in the code text area and press render to get a preview of your button, data included if connected to a simulator.

+

See Also

+

Workflow for Web Deck Design

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Events/index.html b/Extending/Development/Internals/Events/index.html new file mode 100644 index 00000000..26d004e0 --- /dev/null +++ b/Extending/Development/Internals/Events/index.html @@ -0,0 +1,3137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Events - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Events are entities created by decks to report which interaction occurred on the physical device.

+

Events are created by callback functions to enter Cockpitdecks processing.

+

Events have a type that identifies the interaction.

+

Event

+

Base class for events.

+

Attributes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDefinition
deckRelated Deckwhere interaction occured.
buttonRelated button Button Index
actionDeck InternalsDeck action name]]
timestampTime of creation of event
is_processedWhether event has run or not.
+

Common Functions

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FunctionDescription
run(just_do_it: bool = False)Run the event.

If just_do_it is False, enqueue the Event.

Otherwise, execute the activation.
handling()Mark the start of processing.
handled()Mark the end of processing.
+

PushEvent

+

Parameters

+ + + + + + + + + + + + + + + + + + + + + +
AttributeDescription
pressedTrue or False, if key is pressed (True) or released (False)
pulledTrue if push/pull option is enabled and button was pulled rather than pushed.
+

PushEvents are in fact raised twice for an interaction. First when the button is pressed, and second when the button it released. It is a main differentiator of Press and LongPress Events.

+

PressEvent

+

A Press event is a single event sent when a button is pressed. No event is sent when the button is released. It is therefore impossible to know how long the button was pressed.

+

In the case of PressEvent, we only know that the button was pressed for a short time, without being able to set or determine how long it was pressed.

+

LongPressEvent

+

A LongPress event is a single event sent when a button is pressed for a long time, but the time it remained pressed is not defined or settable. No event is sent when the button is released. It is therefore impossible to know how long the button was pressed.

+

In the case of LongPressEvent, we only know that the button was pressed for a long time, without being able to set or determine how long it was pressed.

+

The long time event occurs experimentally after roughly 600 milliseconds. It is therefore safe to assume that for a LongPress event, the button remained pressed for at least 1 second.

+

EncodeEvent

+

Parameters

+ + + + + + + + + + + + + +
AttributeDescription
clockwiseTrue or False, if encoder is turned clockwise (True) or counter-clockwise (False)
+

SlideEvent

+ + + + + + + + + + + + + +
AttributeDescription
valueRaw value of the slider (as produced by the driver)
+

TouchEvent

+ + + + + + + + + + + + + + + + + + + + + +
AttributeDescription
xx-position of the event (horizontal)
yy-position of the event (vertical)
startTimestamp of touch event
+

SwipeEvent

+

A Swipe event is either an event on its own, or the combination of two Touch event.

+

In the latter case, the first Touch event is the start of the swipe.

+

The second one being the end of the swipe, and must be supplied the first event as an argument. In this case, the swipe() method will return a Swipe event that combine them both.

+
+

Streamdeck Touch and Swipe Events

+

Please note that some deck models do no report timing information. In this case, the timing information is either added by Cockpitdecks or not available at it.
+In particular, Streamdeck decks have a particular Swipe event that always last at most less that a second, without any timing information. Just a start position, and an end position taken at most one second after the start event.

+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Hardware Representation/index.html b/Extending/Development/Internals/Hardware Representation/index.html new file mode 100644 index 00000000..8b445b1a --- /dev/null +++ b/Extending/Development/Internals/Hardware Representation/index.html @@ -0,0 +1,2966 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + « Hardware » Representations - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

« Hardware » Representations

+ +

Hardware Representations only exist for web decks.

+

When a web deck tries to reproduce on screen hardware elements only available on physical decks, it uses hardware representation.

+

Let us take a very simple hardware representation: A translucent button with a colored led. The button has sign on it (letter, number, icon…)

+

hardware-representation.png

+

For the physical deck, the deck device driver will handle the rendering of the feedback on that deck through a dedicated representation and its accompanying driver software.

+

Cockpitdecks uses a hardware representation to draw the same button on a web deck. A hardware representation is nothing more than an image used to represent a button on a web deck.

+

Very much like the physical representation, Cockpitdecks uses dedicated software to create the image, but once created, the image of the hardware button is sent to the web deck for drawing like any other image, supplying the image of course, but also its position and size.

+

Since a hardware representation is an image, all hardware representations are in fact particular icons image and treated as such.

+

In the case of our translucent button, all we need is a image of that button when it is not lit. When lit with a given color and/or intensity, the hardware representation software will overlay the button sign to give the illusion that it is lit as requested. The resulting image is sent to the web deck to give the illusion of the lit button.

+

Important Note

+

Hardware representations are often specific to a deck model. Therefore the device driver of the corresponding deck model need to be installed to make some feature parameters available to the hardware representation.

+

Here is an example of the demo deck, with a X-Touch Mini hardware representation for the right most encoder (circular ramp of 13 monochrome leds). In the picture below; on the left side, the encoder is not represented because xtouchmini driver software is not installed. On the right side, it is correctly represented.

+

hr.png

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Instruction/index.html b/Extending/Development/Internals/Instruction/index.html new file mode 100644 index 00000000..ef8b3465 --- /dev/null +++ b/Extending/Development/Internals/Instruction/index.html @@ -0,0 +1,2909 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Instructions - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

An Instruction is some piece of code that needs to be carried out.

+

CockpitdecksInstruction

+

A Cockpitdecks instruction is a internal Cockpitdecks instruction performed inside the Cockpit.

+

The Instruction receives the current Cockpit as a parameter to execute its instruction.

+

SimulatorInstruction

+

A SimulatorInstruction is sent to the Simulator for execution. Examples of SimulatorInstructions are executing of commands, or update of simulator data value.

+

The Instruction receives the current Simulator as a parameter to execute its instruction.

+

ButtonInstruction

+

A ButtonInstruction is a hook to allow for custom, user defined instruction to be executed.

+

The Instruction receives the current Button it is associated with as a parameter to execute its instruction. The Button has programmatic access to the Deck it belongs, to the Cockpit, and to the Simulator.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Internal Datarefs/index.html b/Extending/Development/Internals/Internal Datarefs/index.html new file mode 100644 index 00000000..1697120c --- /dev/null +++ b/Extending/Development/Internals/Internal Datarefs/index.html @@ -0,0 +1,2975 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Cockpitdecks Internal Data - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Cockpitdecks Internal Data

+ +

These datarefs are created and maintained for statistical purpose. They should not be used for simulation, only for development and Cockpitdecks health monitoring.

+
class INTERNAL_DATAREF(Enum):
+    #
+    # C O C K P I T D E C K S
+    #
+
+    #
+    # C O C K P I T
+    #
+    # Number of cockpitdecks reloads
+    COCKPITDECK_RELOADS = "reload_pages"
+
+    #
+    # D E C K
+    #
+    # Number of page reloads
+    DECK_RELOADS = "reload_page"  # /<deck-name>
+
+    # Number of page reloads
+    PAGE_CHANGES = "change_page"  # /<deck-name>/<page-name>
+
+    RENDER_BG_TEXTURE = "bg-texture"
+    RENDER_BG_COLOR = "bg-color"
+    RENDER_CREATE_ICON = "create_icon"
+
+    #
+    # P A G E
+    #
+    DATAREF_REGISTERED = "registered_dataref"
+    PAGE_RENDER = "page_render"
+    PAGE_CLEAN = "page_clean"
+
+    #
+    # B U T T O N
+    #
+    BUTTON_ACTIVATIONS = "activation"
+    BUTTON_RENDERS = "render"
+    BUTTON_REPRESENTATIONS = "representation"
+    BUTTON_CLEAN = "clean"
+
+    #
+    # U D P
+    #
+    INTDREF_CONNECTION_STATUS = "_connection_status"
+    # Number of UDP packet received
+    UDP_BEACON_RCV = "udp_beacon_received"
+    UDP_BEACON_TIMEOUT = "udp_beacon_timeout"
+    STARTS = "starts"
+    STOPS = "stops"
+
+    UDP_READS = "udp_rcv"
+    LAST_READ = "last_read_time"
+    VALUES = "values_read"
+    UPDATE_ENQUEUED = "value_change_enqueued"
+
+    # Average number of reads per seconds (last 100 reads)
+    UDP_READS_PERSEC = "cockpitdecks/udp/persec"
+
+    # Time sice last read
+    UDP_CYCLE = "cockpitdecks/udp/cycle"
+
+    # Average number of dataref values recevied per seconds (last two minutes)
+    UDP_DATAREFS_PERSEC = "cockpitdecks/udp/datarefs_persec"
+
+    # Total number of dataref values recevied
+    UDP_DATAREF_COUNT = "cockpitdecks/udp/dataref-count"
+
+    #
+    # E V E N T
+    #
+    ENQUEUE_CYCLE = "cockpitdecks/udp/enqueue/cycle"
+
+    ENQUEUE_COUNT = "cockpitdecks/udp/enqueue/count"
+
+    ENQUEUE_PERSEC = "cockpitdecks/udp/enqueue/persec"
+
+    #
+    # X - P L A N E
+    #
+    # Zulu diff
+    # Time difference between zulu in sim and zulu on cockpitdecks host computer (in seconds and microseconds)
+    ZULU_DIFFERENCE = "xplane/timedelay"
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Journey to Cockpitdecks/index.html b/Extending/Development/Internals/Journey to Cockpitdecks/index.html new file mode 100644 index 00000000..b5d5f889 --- /dev/null +++ b/Extending/Development/Internals/Journey to Cockpitdecks/index.html @@ -0,0 +1,2845 @@ + + + + + + + + + + + + + + + + + + + + + + + Journey to Cockpitdecks - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

It started with a standard Stream Deck device. I found it nice to be able to see result of interaction with the deck right away on the deck.

+

I only fly Airbus, so I wanted to see annunciators switches on my deck. On panel at a time.

+

So I started to cut icons from an overhead screen dump. I quickly noticed some repetitive annunciators. But also numerous wordings changes. Label above annunciators would also change of course. I did not want to end up with a thousand icons, named by some clever convention. So I gave up fairly quickly on image processing and static icons and headed for a mechanism to dynamically create images with words laid over. I elaborated a set of design conventions that resulted in image generation.

+

Convention evolved quickly as I wanted to not only be able to generate annunciators but also switches, rotary knobs, and display some numbers like FCU data, fuel tank levels, etc.

+

Then I discovered the Loupedeck device. More elaborate with encoders and touch screens. I wanted to be able to handle those devices as well.

+

I am the happy owner of an inexpensive Behringer X-Touch Mini that I wanted to handle with my development too, even if it was not capable of showing images, it had other LED to communicate.

+

This led, one button at a time to what Cockpitdecks is today.

+

One, a gentleman approached me with questions about the configuration of a deck. I was first surprised to see that somebody else was successfully using my code. But above all, this gentleman who was designing cockpits for other aircrafts needed a way to speed up its development process and suggested rendering decks on screen. This led, in a first iteration in what we called « virtual decks », but as the screen rendering package used lacked features for interaction and brought in significant complexity, we decided to change for lightweight rendering in a browser window. Virtual decks became web decks (but in the code or wording, both terms still co-exist.) In a nutshell, rather than sending generated images to decks for display on their small LCD screens, we send them to a web page for display on a Canvas. Couldn’t be simpler!

+

That, together with numerous code refactoring, abstractions, and conventions, lead to a flexible, configurable, autonomous application that now allow to use common deck models with X-Plane fight simulator. As an illustration of code refactoring, Cockpitdecks was first designed to work with X-Plane only, using X-Plane terminology of Datarefs, Commands, etc. Refactoring brought in a new abstractions for Simulator applications, with neutral generic terminology and allow now for different simulation software to be used with Cockpitdecks.

+

Cockpitdecks underwent numerous refactoring processes. Some basic, like name changes, some easy, like abstractions (insertion of intermediate often abstract classes), and some heavy (computer value from data with formula). But, through all these changes, the syntax of config.yaml files for buttons rarely changed. Vocabulary get richer because of addition of activations or representations, but older existing definitions kept working with minimal changes. Pages developed a year ago still work surprisingly well after all these internal changes!

+

Everything was created with fun and heart and is freely available to other to enjoy.

+

Towards the end, we added very basic straightforward user interfaces to interactively create buttons on web pages without coding. While the core functions are working as expected, a web user interface specialist could build a friendlier application from the feasibility rough cut we laid over. My knowledge of web user interface disappeared over time as things were getting highly specialized and refined. I’m sure there is room for a little svelt or vue application if someone is tempted by collaborating. (Backend already exists.) This web-based user interface would allow any one to design buttons, button pages and deck layouts to use with any simulation software. Next to that, built in extension mechanism allow developers to bring their own creations to Cockpitdecks.

+

See Also

+

History

+

center

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Normalisation/index.html b/Extending/Development/Internals/Normalisation/index.html new file mode 100644 index 00000000..4cadd923 --- /dev/null +++ b/Extending/Development/Internals/Normalisation/index.html @@ -0,0 +1,2868 @@ + + + + + + + + + + + + + + + + + + + + + + + Normalisation - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Items here are more in a wish list rather than completed development

+

Button definition

+

Try: button meta (name, etc.) + global parameters + activation attributes + representation attribute name are all at minimal indent level.

+

Representation is a next indentation.

+

Formalize

+

Checl: set-dataref copies the button VALUE to the dataref

+

Value

+

Single value:

+

Only two possibility:

+
    +
  1. A single dataref (then we return the value)
  2. +
  3. A formula (then we return the value of the formula or None if error)
  4. +
+

Multiple values: (check that they are always all dict and not list)

+
    +
  1. Multiple datarefs (multi-datarefs)
  2. +
  3. All values of individual annunciator parts
  4. +
  5. All values of button states
  6. +
+

Introspection

+

Save stats on deck, page, buttons in internal datarefs

+

Save stats on connection, speed of collection, speed of processing, etc.

+

(Try something "à la Oracle": wait time on what?)

+

Cockpits

+

number of plane change

+

number of definition reloads (deck reloads, page reload)

+

time since last start/reload/longest

+

Deck

+

number of page load

+

Page

+

number of load

+

number of button activations

+

Button

+

Number of activation

+

Number of render

+

datarefs

+

number of update

+

If redis? save in redis?!

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Rendering/index.html b/Extending/Development/Internals/Rendering/index.html new file mode 100644 index 00000000..531e5fb3 --- /dev/null +++ b/Extending/Development/Internals/Rendering/index.html @@ -0,0 +1,2858 @@ + + + + + + + + + + + + + + + + + + + + + + + Rendering - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Rendering

+ +

Rendering occurs as follow.

+

Request to render always starts from the Button. The Button solely calls the procedure render() on itself.

+

To render a Page for example, the Page will call render() on each button in turn. It may then call a supplemental procedure to fill unused icons with images.

+

The Button render() call is quickly transferred to the Deck for specific rendering handling. So the Button asks the Deck to render itself:

+
    # In the Button class
+	def render(self) -> None:
+		self.deck.render(self)
+
+

From the button index, the Deck will deduce rendering possibilities. It will then ask the button to supply the data for the rendering.

+
	# In the Deck class
+	def render(self, button: Button) -> None:
+		button_representation = button.get_representation()
+		render_data = button_representation.render()
+		# transform render_data if necessary
+		# send it to device driver for handling (display, sound, coloring...)
+		self.device.handle_data(render_data)
+
+

In other words, the representation of the button is responsible to generate and provide whatever is necessary for the deck device driver.

+

Let us take a very practical example.

+

The deck device driver has a function to display an iconic image on a key. That function expects the index of the key to designate it unambiguously, and an image in JPG format with the proper size 90 × 90 pixels.

+

If we use a generic representation that produces an image, the resulting image will be a 256 × 256 pixel transparent PNG image. We need to convert and resize that image before we can send it to the device driver.

+
	def render(self, button: Button) -> None:
+		brep = button.get_representation()
+		render_image = brep.render()
+		# transform render_data if necessary
+		keyicon = render_image.resize([90, 90]).convert("RGB")
+		# send it to device driver for handling (display, sound, coloring...)
+		self.device.set_key_image(key=button.inedx, image=keyicon)
+
+

This is a very simple example, but it shows the flow of information and the sequence of calls to get the work done.

+

To emit a sound, the representation render() function would be responsible for generating, for example, a sound file in WAV format. The deck render function would then pass that sound to the device driver play_sound() function that expects a sound file to play.

+

To color a LED, the representation render() function would be responsible for generating a color and a light intensity (in 0..1 value range). THe deck render function would then pass those data to the device driver turn_led_on(color, intensity) function to submit appropriate instruction to the device to turn the light on with the desired color and intensity.

+

People familiar with internet web development knowledge can follow the process for (Virtual) Web Decks. The device driver of Web Deck (called VirtualDeck) is a relatively straightforward JSON message generator and ultimately sends the message to the browser for display. In the browser, JavaScript process decodes the message and interpret it to display an image, play a sound, or refresh an entire page. (Communication occurs in a WebSocket channel.)

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Replay/index.html b/Extending/Development/Internals/Replay/index.html new file mode 100644 index 00000000..ed440b74 --- /dev/null +++ b/Extending/Development/Internals/Replay/index.html @@ -0,0 +1,2897 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Replay - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Replay

+ +

Interactions with Cockpidecks enters the application through Events. Deck Events arrive from the user interaction with decks, Simulator Events arrive because of Simulator data modification. Both then follow the same execution path, performing actions and adjusting deck feedback.

+

In the process, these events can be captured and saved for a later, for a replay of the same events for example.

+

That's the purpose of the Replay feature in Cockpitdecks.

+

Each interaction that occurred during the session has been recorded and can be replayed. It can be submitted to Cockpitdecks in a similar way it was submitted when interactions with the deck occurred. Simulator interactions are recorded as well but never replayed, as we expect the Simulator to respond to user interaction in a similar fashion each time.

+

Replay optionally respect the original timing.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/To do/index.html b/Extending/Development/Internals/To do/index.html new file mode 100644 index 00000000..f3cbd4af --- /dev/null +++ b/Extending/Development/Internals/To do/index.html @@ -0,0 +1,2891 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + To Do - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

To Do

+ +

Improve cockpitdecks.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Value/index.html b/Extending/Development/Internals/Value/index.html new file mode 100644 index 00000000..1b5e2c0d --- /dev/null +++ b/Extending/Development/Internals/Value/index.html @@ -0,0 +1,3033 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Value - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Cockpitdecks introduced the concept of a Value.

+

A Value defines where it gets its value from, and make its value always available through the simple abstraction.

+

Every entity that can have a value uses this abstraction.
+The value of a Button,
+The value of a chart or sparkline,
+The value of an Annunciator part.

+

It is a dynamic entity. It does not store any value. It just know where to gets its value from and gets it.

+

It is up to the entity that uses the value to keep a copy, see if it has changed, keep the last ten values… The Value abstraction only knows where and how to find its value when asked to provide it.

+

A Value can report information about its behavior, like for instance the list of datarefs that are necessary to compute its final value.

+

The Value entity performs necessary variable substitution, computations if there is a formula, and can also take care of writing the value to an X-Plane dataref.

+

Value Attributes

+

Here is the list of attributes that are inspected by a Value to determine its value.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDefinition
datarefSingle Dataref, monitored if coming from X-Plane
formulaReverse Polish Notation expression with reference to Cockpitdecks variables such as datarefs, internal datarefs, and internal states.
multi-datarefs (or datarefs, plural form)List of datarefs that are monitored
string-dataref(singular form)String Datarefs
string-datarefsList of String Datarefs fetched as string through a Cockpitdecks helper plugin.
set-datarefDataref where the value is written to
any-attributeIf an attribute is added, Cockpitdecks will look into this very particular attribute for variable substitution. See below.
+

Additional Attribute

+

Cockpitdecks will look into known additional attributes for variable substitution. Additionaly, a button designer can ask that its particular attribute be scanned as well.
+Here is a typical example of such variable scanning:

+
  - index: 5
+    name: SQUAWK00XX
+    type: none
+    formula: ${sim/cockpit/radios/transponder_code} 100 / floor
+    text:
+      text: ${formula}
+      text-format: "{:02.0f}"
+      text-font: 7-segment-display-extended.otf
+      text-size: 50
+      text-position: rm
+      text-color: khaki
+      text-bg-color: (40, 40, 40)
+
+

In the above example, attribute text will be scanned for more datarefs. The attribute that gets scanned always has the same name as the main attribute:

+
text:
+	text: "text to be scanned for ${datarefs}"
+
+

or

+
any-attribute:
+	any-attribute: "additional attribute to be scanned for ${datarefs}"
+
+

A Value will always look into the following attributes:
+- Text
+- Formula
+- Annunciator parts

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Web Decks Internals/index.html b/Extending/Development/Internals/Web Decks Internals/index.html new file mode 100644 index 00000000..a72cc1ce --- /dev/null +++ b/Extending/Development/Internals/Web Decks Internals/index.html @@ -0,0 +1,3200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Web Decks Internals - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Web Decks are designed with simple standard web features, are rendered on an HTML Canvas, uses standard events to report interaction through basic JavaScript functions.
+The application that serves them is a very simple Flask application with Ninja2 templates. The Flask application also runs the WebSocket proxy.

+

webdecks.svg

+

Web Deck Messages

+

Cockpitdecks to Web Deck Messages

+

Send code

+
{
+    "code": code,
+    "deck": name,
+    "meta": {
+        "ts": datetime.now().timestamp()
+    }
+}
+
+

Code is interpreted by the deck. Codes are:

+
    +
  • 0: Display (attached) image on deck/key
  • +
  • 1: Reload yourself (deck)
  • +
  • 2: Emit (attached) sound for deck
  • +
+

meta data is a python dictionary serialized into JSON (mostly name, value pairs). It can contain any arbitrary serialisable items.

+

Send Image

+

It can be a key image, or a «hardware» image.

+
{
+    "code": 0,
+    "deck": name,
+    "key": key,
+    "image": base64.encodebytes(content).decode("ascii"),
+    "meta": {
+        "ts": datetime.now().timestamp()
+    }
+}
+
+

Send Sound

+

It can be a key image, or a «hardware» image.

+
{
+    "code": 2,
+    "deck": name,
+    "sound": base64.encodebytes(content).decode("ascii"),
+    "type": "wav",
+    "meta": {
+        "ts": datetime.now().timestamp()
+    }
+}
+
+

Web Decks to Cockpitdecks Messages

+

Send code

+
{
+    "code": code,
+    "deck": name,
+}
+
+

Code values:

+
    +
  • 0: Report event (see below)
  • +
  • 1: New deck, expect reload/refresh data
  • +
+

Send Event

+
{
+	"code": 0,
+	"deck": deck,
+	"key": key,
+	"event": value,
+	"data": data
+}
+
+

Web Deck Drawing

+

Technically speaking, web decks are very simple browser entities. The web representation is an HTML Canvas. The background image of the deck is laid out as the background image of the canvas. Deck icons are images laid over the background image at precise size and positions. Nothing less, nothing more.

+

Another special kind of icon images can be generated by Cockpitdecks especially for web decks. They are called hardware representations. They behave exactly like other representations, but they only exists for web decks. They usually have particular sizes and positions, and their drawing is sometimes complex.

+

Web decks communicate with Cockpitdecks through standard WebSocket.

+

The connection is established or re-established on startup of Cockpitdecks. Cockpitdecks is aware of the web decks connected to it and only update web decks when necessary. There should never be a need to refresh a web page containing a web deck.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Internals/Workflow for Web Deck Design/index.html b/Extending/Development/Internals/Workflow for Web Deck Design/index.html new file mode 100644 index 00000000..8264a8df --- /dev/null +++ b/Extending/Development/Internals/Workflow for Web Deck Design/index.html @@ -0,0 +1,2976 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Workflow for Web Deck Design - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Here is a suggested workflow for web deck design.

+

Workflow

+

Ideally, X-Plane simulator should be running to get live response data.

+

Get a background image. PNG or JPEG only.

+

Place it in

+
<Aircraft>/deckconfig/resources/decks/images/<image-name>.png
+
+

Start cockpitdecks for that aircraft:

+
$ cockpitdecks-cli <Aircraft>
+
+

Cockpitdecks will start the application server, notice the available background image and Deck Designer will be available.

+

Deck Designer

+

Head to the Deck Designer page at

+
http://<hostname>:7777/designer
+
+

and select the above image.

+

Add interactors like buttons, encoders, and hardware images. Resize interactors, position them over the background image, and name them.

+

Double click inside the button to resize it.

+

Click outside the button to deselect it.

+

Press Delete key when selected to remove it.

+

Double on the label to rename it. Press Enter to quit the edit text area.

+

Save the layout. You can load it later if you wish and continue editing.

+

We recommand creating a button named reload and place it at a convenient location on the image.

+

Saving the layout will create a new Deck Type, and will add a new deck in the config.yaml file, like any other deck, and add it to the secret.yaml file as well.

+
<Aircraft>/deckconfig/resources/decks/types/<image-name>.json
+<Aircraft>/deckconfig/resources/decks/types/<image-name>.yaml
+
+

Reload the decks, which will provoke the reload of virtual web decks as well.

+
+

Web Deck Definitions

+

Web deck definitions are located either in the Cockpitdecks code or in the aircraft deckconfig folder. On page reloads, web decks definitions located in the code are not reloaded. Web deck definitions in the aircraft folder are reloaded.

+
+

Button Designer

+

Head to the web deck home page.

+
http://<hostname>:7777/
+
+

Your new deck appears. In the web deck list, select the newly created deck it will open in a new window.

+

Using the button designer create and test a new button for your new deck selectable at the top.

+

Select the deck, give a name to a layout (default would be default if none provided), and to the page.

+

If you followed our advise, there should be a button named reload, the label we gave it in the Deck Designer.

+

Select reload as Activation.

+

Select icon as Representation. Select icon named "reload.png".

+

Press render to preview rendering.

+

Press save to save the layout/page/button.

+

Reload pages immediately preview the appearance on the new button.

+

From now on, it is now possible to adjust any of the layout, or button definition, save them, reload and use the button.

+

When happy with the final deck, I simply comment out the code of the reload button. You will need it later…

+
index: reload
+type: reload
+icon: reload.png
+
+

A Word of Advise

+

Never ever forget that the goal of these designer tools is currently not to provide you with a final design ready to be used. One day may be. The above designer tools aim at providing you with skeleton files that contain data that is difficult to get or estimate, thereby removing numerous trial and errors attempts.

+

The goal of the Deck Designer is to supply deck image positions and sizes for all items that need displaying on a web deck.

+

The goal of the Button Designer is twofold:

+
    +
  1. Help you test button design right away, viewing a button’s appearance without requiring numerous « deck reload page ».
  2. +
  3. Learning the button design options and capabilities without searching through the code.
  4. +
+

I hope both tools reach their goal and help you design buttons faster for your decks.

+

One more thing…

+

Never ever forget enjoy flying with your home made set up.

+

If Cockpitdecks crashes or is not responding, just restart it. It will restart, reconnect, and reload necessary data. If failures are persistent, just drop us a mail with a description of the problem and Cockpitdecks.log file.

+

Now just go and take off for new adventures.

+

Automation

+

Using an external file watcher, it is possible to provoke a deck reload each time a file has been saved, using curl(1) to post a request for page reload to Cockpitdecks. There is a handy shell script that does exactly this using nodejs nodemon utility.

+
$ nodemon -w aircrafts/*/deckconfig/resources/decks/types -e yaml --exec curl "http://127.0.0.1:7777/reload-decks"
+
+

Moving on from there

+

From there one can:

+
    +
  • Add more buttons, encoders, hardware button.
  • +
  • Adjust the layout, resize buttons, move them.
  • +
  • Save the new layout.
  • +
  • Define and save more button definitions.
  • +
  • Reload the deck to preview.
  • +
+

When the deck is completed, it is advisable to rename it.

+

Change name and cross references to name in config, secret, and layout pages.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Packaging/index.html b/Extending/Development/Packaging/index.html new file mode 100644 index 00000000..03cf018e --- /dev/null +++ b/Extending/Development/Packaging/index.html @@ -0,0 +1,2929 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + (Python) Packaging - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Cockpitdecks is «packaged» (in python's terminology) into a few packages to cleanly split dependencies and requirements.

+

The main package contains Cockpitdecks core engine and virtual web decks.

+

The following simulator packages are available:

+
    +
  • X-Plane (package named cockpitdecks_xp, named xplane)
  • +
+

The following (physical) deck packages are availalble:

+
    +
  • Streamdeck (package cockpitdecks_sd, named streadeck)
  • +
  • Loupedeck (package cockpitdecks_ld, named loupedeck)
  • +
  • Behringer X-Touch Mini (packages cokpitdecks_bx, named xtouchmini)
  • +
+

The following extension packages are also available:

+
    +
  • Weather and METAR (package cockpitdecks_wm, named weather), adds representation to display METAR information as an icon.
  • +
  • Demo Extension (package cockpitdecks_ext, named demoext), add a few specialized representations, sometimes very dependent on a specific deck model.
  • +
+

A single extension can be installed as such:

+
pip install 'cockpitdecks_ext @ git+https://github.com/devleaks/cockpitdecks_ext.git'
+
+

where cockpitdecks_ext is the extension package name

+

or

+
pip install 'cockpitdecks[demoext] @ git+https://github.com/devleaks/cockpitdecks.git'
+
+

where demoext is the extension name.

+

Each extension lives its own evolution and maintenance part. Requirements are precisely set and monitored regularly to ensure smooth cooperation between all packages.

+
+

X-Plane Simulator Extension

+

Currently, X-Plane simulator is the only simulator option available. As such, it is included in the core Cockpitdecks installation. It is not necessary to install it separately.

+
+

Note about Deck Extensions

+

Deck-specific extensions contain both physical deck and virtual web decks. The reason is that some virtual web deck extensions extensively rely on deck device drivers.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Reference/Activation/index.html b/Extending/Development/Reference/Activation/index.html new file mode 100644 index 00000000..43779997 --- /dev/null +++ b/Extending/Development/Reference/Activation/index.html @@ -0,0 +1,2889 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Activation - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Activation

+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Reference/Deck Type/index.html b/Extending/Development/Reference/Deck Type/index.html new file mode 100644 index 00000000..c4cc599a --- /dev/null +++ b/Extending/Development/Reference/Deck Type/index.html @@ -0,0 +1,3569 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Deck Type - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Cockpitdecks discovers about deck capabilities through a Deck Type structure.

+

A deck is presented to Cockpitdecks through a deck definition file called a Deck Type. The deck definition file describes the deck capabilities:

+
    +
  • How many buttons and how they can be manipulated,
  • +
  • How many dials, if they can be turned, or pushed
  • +
  • Feedback LCD screens for icons
  • +
  • Feedback LED, optionally colored
  • +
  • Ability to emit vibration or sound
  • +
  • etc.
  • +
+

Deck Definition

+

Here is for example, a deck configuration file for a Loupedeck LoupedeckLive device.

+
# This is the description of a deck's capabilities for a Loupedeck LoupedeckLive device
+#
+---
+name: LoupedeckLive
+driver: loupedeck
+buttons:
+  - name: 0
+    action: push
+    feedback: image
+    image: [90, 90, 0, 0]
+    repeat: 12
+  - name: left
+    action: swipe
+    feedback: image
+    image: [60, 270, 0, 0]
+  - name: right
+    action: swipe
+    feedback: image
+    image: [60, 270, 420, 0]
+  - name: 0
+    prefix: e
+    action: [encoder, push]
+    feedback: none
+    repeat: 6
+  - name: 0
+    prefix: b
+    action: push
+    feedback: colored-led
+    repeat: 8
+  - name: buzzer
+    action: none
+    feedback: vibrate
+
+

Attributes

+ + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDefinition
nameName used inside Cockpitdecks to identifying the deck model.
driverKeyword identifying the deck software driver class. (See main drivers class above.)
backgroundThe background attribute is an optional attribute only used by web decks. It specifies a background color and/or image to use for web deck representation. See explanation and exemple below.
buttonsThe Buttons contains a list of Deck Button Type Block descriptions.

This attribute is named Buttons, with Button having the same meaning as in Cockpitdecks. A Deck Type Button is a generic term for all possible means of interaction on the deck:

1. Keys to press,
2. Encoders to turn,
3. Touchscreens to tap or swipe
4. Cursors to slide

A list of button types, each ButtonType leading to one or more individual buttons identified by their index, built from the prefix, repeat, and name attribute. See below.
+

Deck Type Button Block

+

A Deck Type Button Block defines either a single button, or a group of identical buttons. For example, if a deck has a special, unique, «Escape» button, it can be defined alone in a Deck Type Button Block. Similarly, if a deck consist of a grid of regularly spaced 6 by 4 keys that are all the same, they can also be defined in a single Deck Type Button Block.

+

Attributes

+ + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDefintion
nameName of the button type.

The name of the button type is

- either the final name of the button, like touchscreen, when there is a single button with that name on the deck,
- or an integer value that will be used to build the button names, in the case the block defines a sets of identical buttons.
actionsInteraction with the button. Interaction can be:

- none: There is no interaction with the button. It is only used for display purpose. (none interaction can be omitted.)
- press: Simple press button that only reports when it is pressed (one event)
- push: Press button that reports 2 events, when it is pushed, and when it is released. This allow for "long press" events.
- swipe: A surface swipe event, with a starting touch and a raise events.
- encoder: A rotating encoder, that can turn both clockwise and counter-clockwise
- cursor: A linear cursor (straight or circular) delivering values in a finite range.

Action can ba a single interaction or an array of interactions like [encoder, push] if a button combines both ways of interacting with it.
feedbacksFeedback ability of the button. Feedback can be:

- none: No feedback on device, or direct feedback provided by some marks on the deck device. (none feedback can be omitted.)
- image: Small LCD iconic image.
- led: Simple On/Off LED light.
- colored-led: A single LED that can be colored.
- multi-leds: Several, single color, LED on a ramp.
- encoder-leds: Special encoder led ramp for X-Touch Mini (4 modes)
- vibrate: emit a buzzer sound.
repeatIn case of a set if identical buttons, repeat if the number of time the same button is replicated along width (x) and height (y) axis.

If only one value is supplied, it is supposed to be [value, 1] array. For vertical layout, specify [1, value] instead.
+

Action

+

An Action is a keyword that represent a physical mean of interaction with a device:

+
    +
  • none: There is no interaction with the button. It is only used for display purpose. (none interaction can be omitted.)
  • +
  • press: Simple press button that only reports when it is pressed (one event)
  • +
  • push: Press button that reports 2 events, when it is pushed, and when it is released. This allow for "long press" events.
  • +
  • swipe: A surface swipe event, with a starting touch and a raise events.
  • +
  • encoder: A rotating encoder, that can turn both clockwise and counter-clockwise
  • +
  • cursor: A linear cursor (straight or circular) delivering values in a finite range.
  • +
+

Feedback

+

A Feedback is a keyword that represent a physical mean for a deck to communicate some information (i.e. provide some feedback):

+
    +
  • none: No feedback on device, or direct feedback provided by some marks on the deck device. (none feedback can be omitted.)
  • +
  • image: Small LCD iconic image.
  • +
  • led: Simple On/Off LED light.
  • +
  • colored-led: A single LED that can be colored.
  • +
  • multi-leds: Several, single color, LED on a ramp.
  • +
  • encoder-leds: Special encoder led ramp for X-Touch Mini (4 modes)
  • +
  • vibrate: emit a buzzer
  • +
+

Action and Feedback Usage

+

Action and Feedback keywords are used to express requirements in respectively Activation and Representation.

+

For example, an Activation will tell, by listing Actions, which types of interaction it requires.

+
class LightDimmer(Activation):
+    """Customized class to dim deck"""
+
+    ACTIVATION_NAME = "dimmer"
+    REQUIRED_DECK_ACTIONS = [DECK_ACTIONS.PRESS, DECK_ACTIONS.LONGPRESS, DECK_ACTIONS.PUSH]
+
+    def __init__(self, config: dict, button: "Button"):
+
+

A deck button that can be used for the LightDimmer activation must have one of the listed action:

+
name: Virtual Deck
+driver: virtualdeck
+buttons:
+  - name: 0
+    prefix: e
+    repeat: [1, 3]
+    action: [encoder, push]
+    dimension: 27
+    layout:
+      offset: [45, 115]
+      spacing: [0, 41]
+
+

Same principle applies to Representation and Feedback.

+
Examples of button names
+

In the case of a single button, the name of the button will be touchscreen and that name needs to be unique for the deck type.

+
name: touchscreen
+
+

In the case of a set of identical buttons, the name of the button will be built from other attributes:

+
name: 5
+prefix: k
+repeat: [4, 3]
+
+

Names of buttons will be: k5, k6, k7, … k16.

+
Feedback Trick
+
+

Trick

+

If a deck has a vibrate capability, it is advisable to declare it as a separate button of interaction, and use that button like any other. Vibrate is a feedback mechanism.

+
+

Deck Type Button Block: Additional Attributes for Web Decks

+

The above Deck Type Button Block attributes are necessary for all decks, both physical and web decks. Web Decks also contain an additional series of attributes that drive the drawing of the deck in a web navigator window.

+
+

Web Deck Drawings

+

For simplicity, Web Deck Drivers are drawn on an HTML Canvas, which is a pixel-driven drawing space. Web Decks are drawn with images and drawing instructions that use the pixel as a unit for display.

+
+

Web decks can have the following types of interactive buttons:

+
    +
  1. Keys (simple press, long press, etc.)
  2. +
  3. Encoders (turned clockwise, counter clockwise)
  4. +
  5. Touchscreen (pressed, swiped)
  6. +
  7. Slider (slid between 2 range values)
  8. +
+

When rendered in a browser window, web deck interaction means are materialised through a changing pointer cursor (arrow, curved arrow (encode), single dot (push), double dot (pull), etc.)

+
+

Background Deck Type Attribute

+

Please read above in this page the background Deck Type attribute used to specify a background image to use for web deck display.

+
+

Attributes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDefinition
dimensionThe dimension attribute can be a single integer value or a list of two values.

It determine the size of the button has drawn on the Web deck.

If the feedback visualisation is an image, the image attribute specifies the characteristics of the image. Xis horizontal and correspond to the width, Y is vertical and correspond to the height.
layoutLayout of the buttons on the web deck canvas.
- Offset
- Spacing
Buttons will be arranged at regular interval, starting from Offset, with supplied spacing between the buttons. Button sizes are specified in the Dimension attribute.
hardwareConfiguration information for a specific drawing representation of this hardware button.
empty-buttonMinimal Button definition to create an empty, dummy button. This dummy button will becreated and used to generate an "empty" hardware representation (completely off). (In other words, if a button with hardware representation is not defined, this definition will be used to create a button.) See below.
optionsComma-separated list of options, a single option can either be a name=value, or just a name, in which case the value is assumed to be True.

options: count=8,active

sets options countto value 8, and active to True. active is equivalent to active=true.
+

Deck Type background

+

In addition to the above button definitions, a deck type may contain a background attribute. This attribute is only used by web decks. The background attribute defines the background of the web page where the web deck will be rendered. The background can either be

+
    +
  1. A PNG image,
  2. +
  3. A solid color and size information.
    +In the case of a PNG image, the size is deduced from the size of the image.
  4. +
+

Attributes

+ + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDefinition
imageName of a PNG image, with extension. No default.
colorColor of the background of the web page. No default.
sizeArray of two values with width and height of the web canvas. Defaults to (200, 100) pixels.
overlayStatic text or image overlay. Not implemented yet.
+

Special Button « Hardware » Representation

+

Some deck buttons need a special representation or drawing to visually reproduce the physical deck equivalent button. Examples of such special representations are

+
    +
  • LoupedeckLive « colored » numbered buttons,
  • +
  • X-Touch Mini LEDs around the encoders.
  • +
+

These highly specific « drawings » are performed on side representations called Hardware Representations.

+

Technically speaking, they behave very much like LCD representations:

+
    +
  • Some screen space is reserved on the canvas to host the representation,
  • +
  • The hardware representation driver produces an image that mimics the hardware button on the send,
  • +
  • Cockpitdecks « sends » the hardware representation image to the web deck for display in the reserved space,
  • +
  • Like any other representation, the hardware representation gets updated each time the underlying values gets updated.
    +Hardware representation only exists for web decks to draw a very specific button.
  • +
+

Empty Button Definition

+

Hardware Representations need to know how to represent themselves in case they are not used or defined on a page. To present nicely, Cockpitdecks needs to know how to draw an "undefined", unused Hardware Representation. To do this, Cockpitdecks uses a trick: It dynamically create an dummy placeholder button from the Empty Button Definition for its Hardware Representation. Basically, only two attributes need to be defined: A type (often set to none) and an attribute that tells its Hardware Representation. Optionally, a default, initial value can be supplied. Sometimes, some mandatory or optional Hardware Representation attributes need to be supplied as well. Here is an exemple of a simple LED representation.

+
hardware:
+	type: virtual-xtm-led
+	empty-button:
+		type: none
+		led: single
+		initial-value: 1
+
+

hardware-representation-empty.png

+

Examples of Deck Type Button Definition Block

+

Single Button Definition

+
name: Virtual Deck
+driver: virtualdeck
+buttons:
+  - name: left
+    action: [push, swipe]
+    feedback: image
+    dimension: [52, 270]
+    layout:
+      offset: [96, 78]
+    options: corner_radius=4
+
+

Multiple Button Definition

+
name: Virtual Deck
+driver: virtualdeck
+buttons:
+  - name: 0
+    prefix: e
+    repeat: [1, 3]
+    action: [encoder, push]
+    dimension: 27
+    layout:
+      offset: [45, 115]
+      spacing: [0, 41]
+  - name: 3
+    prefix: e
+    repeat: [1, 3]
+    action: [encoder, push]
+    dimension: 27
+    layout:
+      offset: [624, 115]
+      spacing: [0, 41]
+
+

Installed Deck Types

+

On startup, Cockpitdecks reports which deck types are available:

+
Cockpitdecks 12.1.1.20241002 © 2022-2024 Pierre M
+Elgato Stream Decks, Loupedeck decks, Berhinger X-Touch Mini, and web decks to X-Plane 12.1+
+
+INFO  start.py:<module>:248: Initializing Cockpitdecks..
+INFO  xplane.py:init:745: aircraft dataref is sim/aircraft/view/acf_livery_path
+WARN  xplane.py:add_datarefs_to_monitor:1171: no connection
+INFO  xplane.py:add_datetime_datarefs:803: monitoring simulator date/time datarefs
+INFO  cockpit.py:add_extension_paths:337: added extension path /Users/pierre/Developer/fs/cockpitdecks_ext to sys.path
+INFO  cockpit.py:add_extension_paths:342: loaded package cockpitdecks_ext and all its subpackages/modules (recursively)
+INFO  cockpit.py:init:234: available simulator: X-Plane
+INFO  cockpit.py:init:237: available deck drivers: xtouchmini, virtualdeck, loupedeck, streamdeck
+INFO  cockpit.py:load_deck_types:999: loaded 20 deck types (Virtual Deck for Development, LoupedeckLive...), 12 are virtual deck types
+INFO  cockpit.py:scan_devices:507: device drivers installed for xtouchmini 1.3.6, virtualdeck (included), loupedeck 1.4.5, streamdeck 0.9.5; scanning for decks and initializing them (this may take a few seconds)..
+INFO  cockpit.py:scan_devices:532: found 1 xtouchmini
+INFO  cockpit.py:scan_devices:532: found 1 loupedeck
+INFO  cockpit.py:scan_devices:532: found 3 streamdeck
+INFO  start.py:<module>:250: ..initialized
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Reference/Environment/index.html b/Extending/Development/Reference/Environment/index.html new file mode 100644 index 00000000..a2835a57 --- /dev/null +++ b/Extending/Development/Reference/Environment/index.html @@ -0,0 +1,2909 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Environment - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Cockpitdecks discover about its computer host and its extensions through a global configuration file called its environment.

+

The environment consists of a few attributes that tells Cockpitdecks about things it cannot discover on its own.

+
    +
  1. Simulator type and location
  2. +
  3. Network communication hosts and ports
  4. +
  5. Extensions to Cockpitdecks
  6. +
+

Most of the above information can be communicated to Cockpitdecks either through operating system environment variables or in the global configuration file.

+

Recall that Cockpitdecks can run either on the same host as the simulation software, or on a separate host connected through the local area network to the host running the simulator.

+

Simulator Type and Location

+

If the simulation software is located on the same host, the XP_HOME environment variable is the operating system folder path to the home directory of the software.

+

If the simulation solftware runs on another computer, XP_HOME does not need to be set or must be set to None.

+

Network Communication

+

Cockpitdecks Extensions

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Reference/Glossary/index.html b/Extending/Development/Reference/Glossary/index.html new file mode 100644 index 00000000..924966e4 --- /dev/null +++ b/Extending/Development/Reference/Glossary/index.html @@ -0,0 +1,2967 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Glossary - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Button

+

A button is a piece of a deck a user can use to provide some interaction.

+

It can be a simple button to press, a small LCD button, an encoder to turn, or a touch screen that can be swiped with multiple fingers.

+

Deck

+

A Deck is a piece of hardware connected to a computer to provide some input by means of buttons, encoders, cursors… A Deck also provide some feedback to the user through colored LED lights, small LCD icon screens, or by emitting sound and vibration.

+

Web Deck

+

A Web Deck is an emulation of a deck in an internet browser window. Cockpitdecks provides a set of web decks that emulate existing decks.

+

Originally, web decks where created to speed up deck page creation and arrangement. But web decks turned out to be so convenient that they are now part of Cockpitdecks. Some users actually only use web decks, without any physical ones.

+

But it is possible to design as create any web deck one can imagine, built from basic components like buttons, encoders, led, and LCD displays. As a proof of this concept, there a entire overhead panel, fully reactive and responsive to the simulator changes.

+

Cockpitdecks allow a designer to create and use any web deck, in the context of the Cockpitdecks application.

+

Layout

+

A Layout is a collection of pages that can be displayed on a deck. On startup, a deck must tell Cockpitdecks which Layout it is going to use.

+

Page

+

A Page is a collection of buttons that are displayed on a deck at a moment.

+

A deck can display different pages of buttons. A button on a page can be used to switch or change a page, leading to a literally infinite number of pages.

+

All pages that can be displayed on a deck are grouped in a folder called the Layout of the deck.

+

Cockpitdecks

+

Cockpitdecks is the name of the application used to control decks connected to a computer.

+

Cockpit

+

The Cockpit is the main entity of Cockpitdecks that controls most parts of the application, from the start to its termination.

+

Activation

+

An Activation is an action that is executed when interaction occurs on a deck.

+

Representation

+

A Representation is an abstraction of an instruction sent to a deck to change its appearance. It can be turning a LED light on or off, playing a sound, or dynamically generating an iconic image and sending it for display on a deck key.

+

Simulator

+

Simulator Data

+

A Simulator Data is a variable value kept and modified in the simulator. Cockpitdecks expressed interest in receiving a notification when the value of the variable changes. The variable is accessed by its name.

+

Simulator Instruction

+

A Simulator Instruction is something the simulator software understand to perform an action. Cockpitdecks generate instructions and submit them to the simulation sofwtware for execution.

+

Simulator Events

+

See below.

+

Event

+

An Event is created to notify Cockpitdecks that something occurred.

+

Simulator Event

+

Simulator events are either sent by the simulator software or created by Cockpitdecks when something occurred in the simulator. There are two major types of Simulator Events:

+
    +
  1. An action has been triggered inside the simulation software.
  2. +
  3. A value has changed.
  4. +
+

Deck Event

+

Deck Events are produced by deck driver software to notify Cockpitdecks that interaction occurred on the deck: A button was pressed, à encoder was turned, a cursor was slid…

+

Instruction

+

An Instruction is a request emitted by Cockpitdecks to perform an action.

+

Simulator Instruction

+

A Simulator Instruction is an order sent to the simulator to alter its behavior. There are two major types of instructions.

+

Command

+

A Command is an instruction to perform an action.

+

This included instructions that simulate key press.

+

Update Value of a Simulator Data

+

The other type of Simulator Instruction is a request to update a value made accessible by the simulator.

+

Cockpitdecks Internals

+

The Maestro of Cockpitdecks is the Cockpit entity. Internal components are interchangeably called Cockpitdecks items or Cockpit items (word is shorter !).

+

Cockpitdecks Internal Data

+

Cockpit or Cockpitdecks or simply Internal Data is an internal Cockpitdecks value mainly kept for statistical or introspection purposes. Button internal state is maintained inside a few Cockpitdecks internal data. (CockpitData, CockpitdecksData, InternalData are synonyms.)

+

End-users and Cockpitdecks developer can define and use internal cockpitdecks data. They all behave like simulator data, propagating changes to their listeners.

+

Cockpitdecks Instruction

+

Cockpit or Cockpitdecks Instructions are instructions triggered and used inside Cockpitdecks and are not forwarded to the simulator. Cockpitdecks instructions are:

+
    +
  • Change/load alternate deck page
  • +
  • Reload decks
  • +
  • Stop Cockpitdecks
  • +
  • Load new aircraft configuration
  • +
  • +
+

Cockpitdecks Events

+

Cockpit or Cockpitdecks Events are generated internally by Cockpitdecks to notify interested parties that something occurred internally, like loading a new page, reloading the decks, or changing the decks because a change of aircraft was detected.

+

Dataref

+

A Dataref is a simulator data inside the X-Plane flight simulator.

+

SimVar

+

A SimVar is a simulator data from Microsoft Flight Simulator. There are several types of SimVar. Please refer to the Flight Simulator SDK for more information.

+

See Also

+

History

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Reference/Representation/index.html b/Extending/Development/Reference/Representation/index.html new file mode 100644 index 00000000..417b5db0 --- /dev/null +++ b/Extending/Development/Reference/Representation/index.html @@ -0,0 +1,2889 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Representation - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Representation

+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Reference/Simulator/index.html b/Extending/Development/Reference/Simulator/index.html new file mode 100644 index 00000000..273e4b53 --- /dev/null +++ b/Extending/Development/Reference/Simulator/index.html @@ -0,0 +1,2889 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Simulator - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Simulator

+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Reference/Switch Drawing Attributes/index.html b/Extending/Development/Reference/Switch Drawing Attributes/index.html new file mode 100644 index 00000000..0e94f72a --- /dev/null +++ b/Extending/Development/Reference/Switch Drawing Attributes/index.html @@ -0,0 +1,2978 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Switch Drawing Attributes - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Switch Drawing Attributes

+ +

Names are self explanatory 😉.

+
angular_step
+base_fill_color
+base_size
+base_stroke_color
+base_stroke_width
+base_underline_color
+base_underline_width
+bgcolor
+button_dent_extension
+button_dent_size
+button_dents
+button_fill_color
+button_name
+button_size
+button_stroke_color
+button_stroke_width
+button_underline_color
+button_underline_width
+cockpit_color
+cockpit_texture
+code
+datarefs
+decor
+decor_color
+decor_width_horz
+decor_width_vert
+double_icon
+draw_labels
+draw_left
+draw_scale
+draw_ticks
+draw_up
+handle_base_fill_color
+handle_fill_color
+handle_off_fill_color
+handle_off_stroke_color
+handle_off_stroke_width
+handle_size
+handle_stroke_color
+handle_stroke_width
+handle_tip_fill_color
+hexabase
+icon_color
+icon_texture
+invert
+label_opposite
+mark_fill_color
+mark_off_fill_color
+mark_off_stroke_color
+mark_stroke_color
+mark_underline_color
+mark_underline_outer
+mark_underline_width
+mark_width
+move_and_send
+needle_color
+needle_length
+needle_start
+needle_tip
+needle_tip_size
+needle_underline_color
+needle_underline_width
+rotation
+screw_rot
+switch
+switch_length
+switch_style
+switch_width
+texture
+three_way
+tick_color
+tick_from
+tick_label_color
+tick_label_size
+tick_label_space
+tick_labels
+tick_length
+tick_space
+tick_steps
+tick_to
+tick_underline_color
+tick_underline_width
+tick_width
+top_fill_color
+top_stroke_color
+top_stroke_width
+type
+vertical
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Reference/X-Plane/String Datarefs/index.html b/Extending/Development/Reference/X-Plane/String Datarefs/index.html new file mode 100644 index 00000000..9b3a13ba --- /dev/null +++ b/Extending/Development/Reference/X-Plane/String Datarefs/index.html @@ -0,0 +1,3007 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + String Datarefs - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Cockpitdecks extensively uses X-Plane UDP for communicating with the simulator. X-Plane UDP suffers from shortcomings, one being its inability to collect several values of an array at once. Values need to be fetched one a a time, which can be not only tedious but also have an impact on performance by requesting numerous single values.

+
+

UDP Dataref Emission and Performance

+

We reasonably established a empiric limit around 50 to 80 individual datarefs values before it has a significant impact not only on X-Plane but also on Cockpitdecks.

+
+

To circumvent this limitation, Cockpitdecks provides a simple mechanism to fetch entire arrays at once. However, this mechanism has voluntarily be limited to arrays of bytes, i.e. character strings. (But the mechanism could be used to fetch arrays of numeric values as well.)

+

The mechanism relies on a small XPPython3 plugin that collects values of datarefs of type string (array of bytes) and broadcast the values on a UDP port, very much like X-Plane UDP does for float values.

+

Button Declaration and Use

+

To use datarefs of type string, a button has to tell Cockpitdecks it want to use such a dataref.

+
index: 1
+name: AIRCRAFT
+label: Aircraft
+type: none
+string-datarefs: ["sim/aircraft/view/acf_ICAO", "sim/aircraft/view/acf_tailnum"]
+text: "${sim/aircraft/view/acf_ICAO}\n${sim/aircraft/view/acf_tailnum}"
+text-font: B612-Bold
+text-size: 18
+
+

Given the particular nature and treatment of string datarefs, they have to be explicitely defined with a specific string-datarefs attribute which expects a list of datarefs of type string as a value.

+

These datarefs will be collected by Cockpitdecks’ custom mechanism and can then be used like any other datarefs.

+

Important Note

+

A string dataref must be declared first with an explicit string-datarefs attribute.

+

If Cockpitdecks first encounter a statement like this:

+
text: ${some/dataref}
+
+

it will create a reference to a non string dataref some/dataref.

+

It Cockpitdecks later finds a definition

+
strings-daterefs: [ some/dataref ]
+
+

it will not create a string dataref, because an earlier definition of the same dataref declared it as a non string dataref. The solution is to never forget to explicitely declare string datarefs as they occur:

+
strings-daterefs: [ some/dataref ]
+text: ${some/dateref}
+
+

XPPython3 Plugin

+

To circumvent limitations of UDP dataref fetch, Cockpitdecks is bundled with a plugin that reads "string"-typed datarefs efficiently and post them on a UDP port, as a single decoded string value, very much like other dataref values sent by the simulator.

+

The String Dataref Poster is a XPPython3 plugin that reads the string-datarefsattribute in the main Cockpitdecks configuration file. The string-dataref attributes is a list of datarefs that are supposed to be of type "array of bytes", each byte being assumed to be a character ASCII code.

+
string-datarefs:
+  - AirbusFBW/FMA1w
+...
+  - AirbusFBW/FMA3a
+use-default-string-datarefs: True
+
+

The String Dataref Poster will ask the plugin to post the value of each of those datarefs at regular intervals, typically every 5 seconds.

+

String dataref collection is an expensive operation that requires numerous dataref reads. It should be scheduled to run periodically but not too often.

+

The XPPython3 plugin proceeds as follow. For the aircraft currently being used by X-Plane, it reads the Cockpitdecks configuration in the deckconfig folder, if any.

+

If it finds one, it inspect decks, their layouts, pages in these layouts, and ultimately buttons being used on each page, scan for string-datarefs attributes in button definitions, and collects string datarefs that are used. It then collects the values of those datarefs and publish them on a dedicated UDP port, all at once at a regular interval. No more, no less.

+

Cockpitdecks Internal Mechanism

+

Cockpitdecks treats string datarefs a little bit differently. When encountered in a button definition, they are created first with their particular type (string). Other datarefs are created after.

+

Also, string dataref values are not collected throught X-Plane UDP but by another similar mechanism running on another UDP port, collecting values sent by the XPPython3 plugin.

+

A part from those internal differences, string datarefs behave like any other datarefs, notifying the buttons that rely on them when their value is updated.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Reference/X-Plane/X-Plane Cockpitdecks Plugin/index.html b/Extending/Development/Reference/X-Plane/X-Plane Cockpitdecks Plugin/index.html new file mode 100644 index 00000000..5c76e042 --- /dev/null +++ b/Extending/Development/Reference/X-Plane/X-Plane Cockpitdecks Plugin/index.html @@ -0,0 +1,3034 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + X-Plane Cockpitdecks Plugin - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

X-Plane Cockpitdecks Plugin

+ +

Why an Additional Plugin?

+

X-Plane UDP has some shortcomings that prevent some operations with decks.

+

To circumvent this, Cockpitdecks provides a small python plugin called the Cockpitdecks Helper plugin, that need to be installed into X-Plane. The Cockpitdecks Helper plugin will execute some instructions on behalf of the Cockpitdecks application. Cockpitdecks Helper plugin just need to be installed and will provide its services to Cockpitdecks.

+

If not installed, some of the commands inside Cockpitdecks will work properly.

+

Cockpitdecks Helper plugin is an in-process plugin, running inside X-Plane, while Cockpitdecks is an out-of-process executable running independently of X-Plane.

+

Here are the functions the Cockpitdecks Plugin offers to complement X-Plane UDP access.

+

Long command execution

+

Some commands cannot be executed directly through UDP. For exemples, commands that have a start and an end cannot be started or ended though UDP. It is an X-Plane UDP limitation.

+

To execute long press commands, the Cockpitdecks Helper plugin needs to be installed in XPPython3 PythonPlugins folder.

+

String Datarefs

+

X-Plane UDP only allows to fetch dataref values one by one. Retrieving a string is a tedious process because each character has to be fetched individually.

+

To collect string-typed datarefs, the Cockpitdecks Helper plugin needs to be installed in XPPython3 PythonPlugins folder. It collects the entire string and then broadcasts it as a whole string, not individual characters.

+

See String Datarefs for details about this.

+
+

To circumvent limitations of UDP, a plugin is necessary to execute commands that require to be activated for a long time (like long push commands).

+

For each long push command, the plugin will create a pair of commands that will be issued at the beginning of the command, and at the end.

+

The plugin scans deckconfig folder for button definitions that contain longpressactivation. It then creates a pair of commands for the command specified in the command attribute.

+
  - index: 27
+    type: long-press
+    label: "APU\nTEST"
+    push-switch:
+      button-size: 120
+      button-fill-color: black
+      button-stroke-width: 0
+      witness-size: 0
+    command: AirbusFBW/FireTestAPU
+    formula: 1
+
+

The above button definition will create the pair of commands

+

AirbusFBW/FireTestAPU/begin

+

and

+

AirbusFBW/FireTestAPU/end

+

AirbusFBW/FireTestAPU/begin will be called when the button is pressed, AirbusFBW/FireTestAPU/end when released.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Reference/X-Plane/X-Plane Interaction/index.html b/Extending/Development/Reference/X-Plane/X-Plane Interaction/index.html new file mode 100644 index 00000000..6b174b67 --- /dev/null +++ b/Extending/Development/Reference/X-Plane/X-Plane Interaction/index.html @@ -0,0 +1,2987 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + X-Plane Interaction - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

The interaction with X-Plane is two folds.

+
    +
  • Execution of commands through Activations.
  • +
  • Monitor simulator parameter values and enqueue value updates.
  • +
+

But let's first explain how Cockpitdecks connects to X-Plane.

+

X-Plane UDP Connector

+

X-Plane connector through UDP uses the following threads of execution to permanently collect dataref values from X-Plane as requested by deck displays.

+

Connect Loop

+

The connect loop monitors the connection to X-Plane.

+

If there is no connnection, if it cannot connect to X-Plane to submit a command, or if it does not receive any data periodically, the connect loop attempts to connect until it succeeds.

+

When a new connection is established, the dataref collection loop is started and buttons get notified of dataref value changes.

+

The connect loop is created and managed by the XPlaneBeacon class.

+

Activations

+

When a button is activated, Cockpitdecks will either submit a command, or ask to change the value of a writable dataref, or both.

+

It will also, optionally, execute another view command. The purpose of the view command is to modify the focus proposed on the screen consecutively to the first command executed. For example, when the Status ECAM button is pressed, the view command changes to show the ECAM display.

+

Dataref Value Monitoring

+

Dataref value monitoring occurs in two steps.

+

First, when a page is loaded in a deck, all dataref it uses are registered with the Dataref Monitor.

+

Inside the dataref monitor, the dataref collection loop permanently collects data from X-Plane and notifies buttons of dataref value updates.

+

If it cannot connect to X-Plane, or if the data collection otherwise fails, a time out occurs.

+

When several times out have occurred, the collection process disconnects and terminates. (There is no need to monitor dataref values if we cannot collect them.) It will be restarted by the connect loop once a new connection is established.

+

The dataref collection loop is created and managed by the XPlaneUDP class.

+

The dataref collection loop immediately compare a dataref value with its previous value and enqueues a dataref change event into the dataref change queue.

+

About Performances

+

Volume

+

Decks sometimes have up to 30 buttons, and simulator pilots sometimes have several decks. During developments, Cockpitdecks controlled up to 6 decks. This led to the monitoring of an average of 80 different datarefs to change the appearance of 80 buttons or LED encoders.

+

We only request dataref values to be sent at the lowest emission rate, that is once per second.

+

A UDP packet of values contains at most 14 or 15 values. It takes a few packets to get all dataref values.

+

We experimentally observed that there is a performance limit of about ~100 datarefs that can be requested from X-Plane. After that, UDP packets will be emitted, but with some noticeable delay (up to a few seconds). And since they will be emitted late, their value will no longer reflect the current X-Plane value.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Development/Reference/X-Plane/X-Plane/index.html b/Extending/Development/Reference/X-Plane/X-Plane/index.html new file mode 100644 index 00000000..e3c2ef80 --- /dev/null +++ b/Extending/Development/Reference/X-Plane/X-Plane/index.html @@ -0,0 +1,2910 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + X-Plane - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

X-Plane

+ +

X-Plane is the name of a flight simulation software edited by Laminar Research.

+

For developers, X-Plane offers the following facilities:

+
    +
  • A Developer API which consists of library calls to integrate a development inside X-Plane.
  • +
  • A network API, which outputs simulator data in UDP packets, and accepts on input some basic instructions to execute named commands, or change simulation internal variables and values.
  • +
  • Another network API, a Web REST API to access internal variables and values.
  • +
  • Most, if not all, configuration files are documented text files.
  • +
+

X-Plane maintains and exposes a large number of internal variables, called datarefs. X-Plane. For a running instance of X-Plane, there can be as much as 10,000 datarefs.

+

X-plane also produces a limited number of internal messages to notify of occurence of special events like another plane has been loaded by the user, orr the VR mode has been enabled. X-Plane messages are related to the X-Plane environment and program. There is no message related to the simulation itself. There is no message to tell a plane has taken off, or brought its landing gear down. There are only messages related to the program: New scenery was loaded, new plane was loaded, new sound bank was loaded, X-Plane has crashed (!), etc.

+

Cockpitdecks has three methods to get variable values from X-Plane.

+
    +
  1. On startup, when a fairly static value needs to be fetched, the (newer) Web REST API is used to get the value of a dataref.
  2. +
  3. The regular method to get values of internal datarefs is a two-step process: First Cockpitdecks notifies X-Plane that it is interested in getting the value of a give dataref, and second, X-Plane sends the value at regular intervals and Cockpitdecks capture the value and interpret it.
  4. +
  5. String values that can change (annunciator displayed strings, path to resources, etc.) are collected by s special, dedicated X-Plane plugin that broadcast on the network (UDP) the values of these variables at regular time interval. Cockpitdecks captures and interpret the values of these string datarefs. This mechanism is limited to string-typed datarefs.
  6. +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Examples/index.html b/Extending/Examples/index.html new file mode 100644 index 00000000..06620a0d --- /dev/null +++ b/Extending/Examples/index.html @@ -0,0 +1,2892 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Examples - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Examples

+ +

When started with no aircraft folder supplied, Cockpitdecks starts a demonstration web deck called Cockpitdecks. It is a 4x3 deck with encoders at the bottom. It demonstrate several of Cockpitdecks capabilities. The definition of the buttons can be used as a basis, as examples, to define yours.

+

e1.pnge2.png

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Extending Cockpitdecks/index.html b/Extending/Extending Cockpitdecks/index.html new file mode 100644 index 00000000..4f53e811 --- /dev/null +++ b/Extending/Extending Cockpitdecks/index.html @@ -0,0 +1,3042 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Extending Cockpitdecks - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Cockpitdecks can be extended in several directions.

+

Extension Possibilities

+

New Button Appearances

+

The most common extension will be adding custom Representations.

+

A Cockpitdecks developer may add a new type of feedback, very much like the Weather buttons. Those buttons do not exist in aircrafts, they were created to artificially enhance the simulation experience and ease other parts of the simulation. Similarly, there are endless opportunities to create other types of buttons and materialize them on a deck though Cockpitdecks. In Cockpitdecks, this is performed by mainly creating new Representations.

+

New Deck Model, New Activations

+

New deck models may appear. Adding a new deck model highlights necessary steps.

+

In addition of defining a new deck model, it might be necessary to add a new method of interaction that this deck may provide. In this case, it might me necessary to develop specialized, custom activation mechanism not foreseen by the current Cockpitdecks version.

+

New Simulator Software

+

Core class Simulator can also be extended to connect Cockpitdecks to another simulation software. In this case, interaction models imposed by Cockpitdecks (datarefs, commands, UDP connection) inspired by the X-Plane flight simulator software may have to be adjusted as well.

+

Extensions Integration

+

The above descriptions enumerates parts of Cockpitdecks that can be extended. To integrate those extensions into Cockpitdecks, there are two mechanisms.

+
    +
  1. Simple addition of files (for new deck types)
  2. +
  3. Addition of python packages (for code, etc.)
  4. +
+

Adding Custom Deck Types

+

The first mechanism is limited to simpler extensions like providing new deck types or models. In this case, it is sufficient to create and provide a new Deck Type file. This also applies to defining new web decks.

+

First, in normal operations, when an aircraft configuration is loaded, Cockpitdecks will search for deck types in the

+
<Aircraft Folder>/deckconfig/resources/decks
+
+

folder. If new deck types are found, they are loaded and made available.

+

Second, if a new deck type is available for all aircrafts, it is possible to create a similar folder organisation at another location and to point the cockpitdecks environment variable to it.

+
COCKPITDECKS_EXTENSION_PATH:
+  - /Users/xplane/cockpitdecks/mydecktypes
+
+

On this case, Cockpitdecks will look for a folder named decks/resources/types at the above location and load deck types defined there.

+

Adding Code

+

The second extension mechanism requires adding (python) code to Cockpitdecks.

+

In this case, the extension mechanism proceeds by providing extension packages to Cockpitdecks, either by pointing at a folder that contains the package, or by mentioning its name if the package has been loaded by another method.

+

Providing extension by location:

+
COCKPITDECKS_EXTENSION_PATH: /Users/xplane/cockpitdekcs/myextension/mypackage
+
+

will loaded package named mypackage from the above folder. (In this case, the above folder path will be added to python sys.path and package will be discovered from there.)

+

Providing extension name:

+
COCKPITDECKS_EXTENSION_NAME: customext
+
+

will load package customext.

+

Cockpitdecks will recursively load the entire extension packages on startup, discover and add extensions provided by those packages.

+

Depending on the extension provided, discovery is achieved as follow:

+
    +
  • New activations (python classes) needs to be derived from the cockpitdecks.buttons.activation.Activation class.
  • +
  • New representations needs to be derived from the cockpitdecks.buttons.representation.Representation class.
  • +
  • New deck (python classes) needs to be derived from the cockpitdecks.deck class.
  • +
  • New simulator software needs to be derived from the cockpitdecks.Simulator class.
  • +
+

Cockpitdecks will discover daughter classes of the above core classes and add them to its collections of available resources.

+

In addition to the above code, new deck types can be added like described above, by providing the following structure inside an extension package:

+
<extension-name>.decks.resources.types
+
+

Resources files located in that module will be loaded as Deck Types.

+

Tools for Developers

+

Cockpitdecks incudes a few developer specific activations that can be used to introspect Cockpitdecks while running.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Extending/Web Decks/index.html b/Extending/Web Decks/index.html new file mode 100644 index 00000000..a4de01d4 --- /dev/null +++ b/Extending/Web Decks/index.html @@ -0,0 +1,3072 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Adding web decks - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

In addition to real physical decks, Cockpitdecks offers Web Decks rendered in a browser window.

+

Web Decks were first developed as a tool to speed up deck page creation, but Web Decks turned out to be fast and available everywhere, in particular on tablets.

+

virtualdecks.jpg

+

A Web Deck behaves like their physical counterparts, with limitations inherent to presentation in a browser window. In a nutshell, a web deck has an optional background image (or a uniform background color), and buttons laid over it. Buttons can be of four main types:

+
    +
  • Buttons that have an image or iconic representation and that can be pressed,
  • +
  • Encoders that can be turned, pushed and pulled,
  • +
  • Surfaces that behaves more or less like touchscreens, touched by the mouse pointer,
  • +
  • Hardware representation, that are deck-specific images that can be placed to mimic the appearance of the deck, with changing states.
  • +
+

A web deck is added like any other deck.

+

Like its physical counterpart, it first must be named and defined, that is Cockpitdecks must know how many buttons are available, of what type, how can users interact with those buttons, etc. While this information is sufficient for physical decks, web decks need a little more information to express how buttons, encoders, and touchscreens are positioned on the deck. That is also where we supply a background image.

+

Second, the named web deck need to be added to the list of decks available to an aircraft, in the deckconfig/config.yaml file.

+

If a Web Deck is detected in an aircraft configuration, Cockpitdecks will automagically start a basic web server to present the Web Deck to the user. The index (home) page of the web server will present all web decks available to the user for that aircraft. Selecting on web deck will start it in another browser window.

+

Web decks are very handy to design layout for decks a developer does not necessary owns.

+

Web decks can be (almost) any physical deck represented in Cockpitdecks (most popular brands and models are actually provided with Cockpitdecks), or any next deck model of your fantasy. As an example and proof of concept, Cockpitdecks comes with a fully operational overhead panel for Toliss A321 neo aircraft. Yes, you read it right, you can have Toliss A321 overhead panel on a touch screen or tablet.

+

Web decks that reproduce existing physical decks like Stream Decks and Loupedeck LoupedeckLive are bundled together with their physical counterparts. To use virtual Stream Decks, you must install the streamdeck extension to Cockpitdecks.

+

Web Deck Declaration and Use

+

Virtual decks need to be declared at two places:

+
    +
  1. Definition of the deck, number of buttons, icon sizes, and layout of buttons. This allows Cockpitdecks to determine the web deck capabilities and key arrangement for drawing, like any other real physical deck.
  2. +
  3. Use of the deck, like any regular deck, with its Layout and is configuring parameters. To use a web deck it must appear in the list of decks in the deckconfig/config.yaml file of an aircraft.
  4. +
+

Web Decks communicate with Cockpitdecks through Websockets. A WebSocket proxy server control Interactions between Web Decks and Cockpitdecks.

+

Web Deck Definition

+

Web decks can be defined at two locations. Cockpitdecks defined a few web decks to help development of physical decks. They are provided with Cockpitdecks and are not meant to be altered by users.

+

However, a cockpit designer may want to add her or his own web deck. To do so, and to prevent adding files to Cockpitdecks source code, it is possible to define additional web deck types in the deckconfig folder of an aircraft.

+
deckconfig
+  ⊢ resources
+    ⊢ decks
+	  ⊢ types
+	    ⊢ custom_deck_type.yaml
+	 ⊢ images
+	    ⊢ custom_background_image.png
+
+

Virtual web decks are defined like any other deck. Their driver must be virtualdeck. They contain additional attributes to render them on a HTML web Canvas like background image or color.

+
name: Virtual Deck 3×2
+driver: virtualdeck
+buttons:
+    - index: 0
+      action: push
+      position: [55, 25]
+      feedback: image
+      image: [96, 96]
+      options: rounded=8
+    - ...
+
+background:
+  color: "#222"
+  image: loupedeck.live.png
+  size: [800, 600]
+  overlays:
+    - text: Hello, world
+      position: [840, 40]
+      font: anexistingwebfontalreadyloaded
+      size: 20
+      color: white
+    - image: logo-transparent.png
+      position: [840, 40]
+
+

Please refer to the Deck Internals for references on how to create and define web decks.

+

Web Deck Usage

+

Web Deck Declaration

+

To use a web deck for a given aircraft, it need to be declared in the list of decks available for that aircraft.

+

deckconfig/config.yaml

+
# Definition of decks for Toliss A321
+#
+aicraft: Toliss 
+decks:
+  - name: Vdeck1
+    type: Virtual Deck 3×2
+    layout: devlayout
+
+

The same web deck definition can be used more than once to create similar decks:

+
decks:
+  - name: Vdeck2
+    type: Virtual Deck 3×2
+    layout: test version
+
+

Web decks include their name in the messages they send to Cockpitdecks. This allows Cockpitdecks to identify the deck that sent the message.

+

deckconfig/secret.yaml

+

If several web decks are running, it is necessary, like other decks, to specify their serial numbers in the secret.yaml file.

+
+

Serial Number of Web Decks

+

The serial number of web decks must be specified in the secret.yaml file. It can be any number or string, but it must be different for each web deck.

+
+
Vdeck1: deck1
+Vdeck2: deck2
+
+

Web Deck Application Server

+

The Web Deck application server is automagically started when Cockpitdecks starts if there are web decks to serve.

+

Web Deck List (Web Deck Home Page)

+

When started, the web decks server offers a Welcome page with all web decks available to the user. Selecting a deck starts it in another web navigator window.

+

webdeck-list.png

+

Web Deck Example

+

Here is a web deck, carefully designed to represent an existing Elgato Stream Deck MK.2 deck. Cheaper.

+

webdeck-example.png

+

Web Deck Example (Complex)

+

This example shows that there is no limit on background and deck type definition if you are patient and meticulous. Here is a fully working exemple of Toliss A321neo Overhead Panel virtualized with Cockpitdecks.

+

Deck Type

+
name: Virtual A321neo Overhead
+driver: virtualdeck
+buttons:
+  - name: apumaster
+    action: push
+    feedback: image
+    dimension: [40, 40]
+    layout:
+      offset: [560, 855]
+  - name: apustart
+    action: push
+    feedback: image
+    dimension: [40, 40]
+    layout:
+      offset: [560, 918]
+background:
+  color: black
+  image: a321neo.overhead.png
+
+

deckconfig.yaml

+
decks:
+  -	name: A321 Overhead
+	type: Virtual A321neo Overhead
+	layout: overhead
+
+

overhead/index.yaml Page

+
buttons:
+  - index: apumaster
+    name: APUMASTER
+    type: push
+    annunciator:
+      size: full
+      model: B
+      parts:
+        B1:
+          text: "ON"
+          color: deepskyblue
+          framed: true
+          size: 60
+          formula: ${AirbusFBW/APUMaster}
+    command: toliss_airbus/apucommands/MasterToggle
+  - index: apustart
+    name: APUSTART
+    type: push
+    annunciator:
+      size: full
+      model: B
+      parts:
+        B0:
+          text: AVAIL
+          color: lime
+          formula: ${AirbusFBW/APUAvail}
+        B1:
+          text: "ON"
+          color: deepskyblue
+          framed: true
+          size: 60
+          formula: ${AirbusFBW/APUStarter}
+    command: toliss_airbus/apucommands/StarterToggle
+
+

webdeck-a321.png

+

Pay attention to the center lower annunciator, lightly white-framed. They are produced by Cockpitdecks and reflect the status of Toliss' annunciators.

+

If buttons have been created for other, physical decks, designing the entire overhead panel is a matter of:

+
    +
  1. using Deck Designer to spot all annunciators, encoders, buttons, etc. and NAME them.
  2. +
  3. copy/pasting button deck definitions and renaming the button index to the above name.
  4. +
+

Nothing more. Nothing less.

+

You can then hang a touchscreen over your head and display the above web page full screen.

+

Cost: 1 touch screen, glue. Protection helmet optional, depending on your trust in the glue.

+

You can consider web decks as live, interactive, responsive images. You can press buttons on the image, Cockpitdecks will adjust the image and its overlays to reflect X-Plane status.

+

Taxi safely.

+

Important Note

+

As explained, Web Deck were originally created has a development tools to speed up button creation and testing. It was suggested by Duane.

+

Once we settle on an architecture, and made critical web-oriented decision (use of HTML Canvas), it took no more that 2 days to get it working! Mainly because of the strict Cockpitdecks architecture and concepts. A few more days were necessary to open up the creation of web decks for Cockpitdecks creators, and imagine Hardware Representation.

+

As today, Cockpitdecks can work with no physical decks, with just Web Decks, even those that replicate existing deck models like Stream Deck and Loupedeck.

+

Please note that web deck lead to Instructions. Currently, Instructions are limited to flight simulation to issue commands, change values, etc. Nothing would prevent a developer from creating Instructions that execute command at the operating system level, like opening a file or starting an application.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/History/index.html b/History/index.html new file mode 100644 index 00000000..1ba4df7e --- /dev/null +++ b/History/index.html @@ -0,0 +1,2882 @@ + + + + + + + + + + + + + + + + + + + + + + + + + History - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

The project started from the multiplication of accessory devices. Some are very dedicated and allow for little flexibility. mini•COCKPIT devices fall into that category. Some other devices are very generic and allow for customisations. Stream decks, Loupedeck devices or other MIDI-based devices fall into this category.

+

I created a single software to allow for the second category of devices to be used with X-Plane. This software relies on conventions to cope with all options and particularities of those devices. That is what I tried to bring into this project.

+

The history of this project explains decisions and processes that were taken to cope with the diversity of devices.

+

A first step was clearly to abstract particularities of device. Pressing keys, rotating knobs or encoders, sliders, touch screens are all different means to interact with some devices. Little iconic display, colored buttons, or multi-led ramps are all different means to communicate feedback to the user. This leads to two abstractions for interaction input (what is called Activation) and feedback output (what is called Representation).

+

A second step was to isolate this software from all device particularities and specificities. This leads to abstractions such as Deck (make, models, device drivers to interact with the device) and Buttons (things a user interact with on a device). A simple abstract model describe the device capabilities and physical organisation (the Deck).

+

A final step was to isolate all interactions with flight simulation software: Issuing commands and monitoring data values coming from it. This is done in a collection of specialised Simulator, Dataref, and Command abstractions.

+

Other entities glue things together with concepts like the Cockpit which is a collection of one or more decks, and a Page that is a grouping of buttons represented at once on a deck.

+

I hope this piece of software will allow you to make a better desktop cockpit and make your flight simulation more enjoyable.

+

See Also

+

Bio on X-Plane Forum

+

center

+

Journey to Cockpitdecks

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Layout/index.html b/Layout/index.html new file mode 100644 index 00000000..21f5dfc3 --- /dev/null +++ b/Layout/index.html @@ -0,0 +1,2953 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Layout - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

A Layout is a collection of Pages that will be displayed on that deck.

+

Layouts were created to cope with different deck models. If you have a set of 30 commands, you can display them all on a 32 key deck on the same page. But you have to spread 30 commands on two pages of buttons if your deck can only display 16 buttons at a time. Same commands, but two different layouts.

+

A Layout is a folder, inside the deckconfig main folder. The Layout is named and addressed by the name of that folder.

+

deck-layout-page-button.png

+

Layout Folder

+

The Layout folder contains the following files:

+
  ⊢ live
+    ⊢ config.yaml
+    ⊢ page1.yaml
+    ⊢ page2.yaml
+
+

The Layout name is live, it contains 2 pages.

+

Layout config.yaml File

+

The config.yaml file inside a layout folder defines Layout-level attributes. The file is optional.

+
# This is at layout level
+default-icon-color: (94, 111, 130)
+default-label-color: blue
+default-label-font: DIN Bold.ttf
+default-label-size: 13
+default-page-name: page1
+
+

Attributes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDefinition
default-icon-color: blueOptional. Default color to use for icon background.
default-label-color: whiteOptional. Default color to use for layout labels.
default-label-font: D-DIN.otfOptional. Default font to use for layout labels.
default-label-size: 13Optional. Default label size.
default-homepage-nameOptional. Default page name in layout.
+

The default values of attributes (like font, colors, and sizes) are fetched at the Cockpit level if they are not specified at the Layout level.

+

Layout attributes are used for all pages in the layout, unless a Page refines the definition of one of these attribute.

+

Pages

+

All other Yaml files in the folder are considered to be Pages in the layout.

+

Default Layout

+

If a deck has no layout specified, Cockpitdecks will generate one with one default page that will display a logo image on the deck (if it is capable of displaying images…) and use the first available push button to toggle X-Plane Map On or Off.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Page/index.html b/Page/index.html new file mode 100644 index 00000000..c6a1767b --- /dev/null +++ b/Page/index.html @@ -0,0 +1,3003 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Page - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

A Page is a Yaml file. It defines is a collection of button definitions that will be installed and used on the deck at a given moment.

+

Page

+

A deck displays one Page of buttons and allow end-users to press those buttons to trigger actions. Actions to be performed when a button is manipulated are defined in the button definition, together with the appearance of the button on the deck. Actions are constrained by the type of push button or encoder. A push button cannot be rotated. The appearance of the button is also constrained by the feedback ability of the deck: Image icon, LED, colored LED, or, sometimes, no display at all.

+

Attributes

+

A Page contains the following attributes:

+
name: Index
+buttons:
+  - index: knobTL
+    name: FCU Airspeed
+    type: knob
+    commands:
+      - sim/autopilot/airspeed_down
+      - sim/autopilot/airspeed_up
+    options: dot, nostate
+includes: views
+fill-empty-keys: true
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDefinition
nameName of the Page. Superceedes the file name. Case sensitive.

If no name is given, the name of the page is the page file name (without the .yaml extension.)

For a given layout, all pages must have different names, otherwise an error is reported, and the page with the same name is ignored.
buttonsMandatory.
A list of button definitions. Please refer to the Button Section for details about button definitions. If there is no buttons attribute, the page as no button to represent and is ignored.
includesIncludes is a list of other pages to include in this page.

Included pages are first merged inside the main page, and then submitted for display and use.
fill-empty-keysOptional, default to False, whether to fill unused keys with a default button definition as a placeholder.

The default value of some attributes (like font, colors, and sizes) is fetched at the Layout level if they are not specified at the Page level.
default-*Default values to be used at the Page-level for display.
+

Page Buttons

+

The buttons attributes contains all button definitions.

+

Default Page

+

If no Page is found in the Layout folder, Cockpit deck will create a default page which consists of a logo displayed on the deck (if the deck is capable of displaying images).

+

The first available button will be programmed to toggle the X-Plane Map On or Off.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Resources/Observables/index.html b/Resources/Observables/index.html new file mode 100644 index 00000000..aad142fd --- /dev/null +++ b/Resources/Observables/index.html @@ -0,0 +1,3019 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Observables - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Observables are data that is constantly monitored. When the data matches a criteria, a list of actions or commands is executed.

+

For people familiar with the concept, it can be considered a if-this-then-that instruction. For example: If the weather data from the simulator indicates rain is falling on the aircraft, we can set the windshield wipers automatically.

+

Observables are defined at the aircraft level.

+

Definition

+

The file observables.yaml is located in the deckconfig/resources folder of an aircraft. It is loaded on startup.

+
observables:
+  - name: example
+    trigger: onchange
+    dataref: some/dataref/to/check
+    actions:
+      - command: do/some/action
+        delay: 5
+
+

Attribute

+

Observables

+

The observables attribute is a list of individual observables.

+

Observable

+

Attributes

+

Trigger

+

True/False Evaluation

+

The trigger attribute of an observable is a single dataref or a formula that is evaluated each time a dataref mentioned in the formula changes. The result of a the formula is compared to 0, i.e. True (different from zero) or False (equal to zero).

+

If the result of the formula is True, commands listed into the actions attributes are executed.

+

Value Changed

+

Another method to trigger the flow of action(s) is to select the value changed mode. Actions get executed as the value of the dataref or of the computation has changed.

+

Observable

+

The observable is the data that is monitored. It can either be a single dataref or a formula.

+

Actions

+

The actions attribute is a list of individual actions that gets carried out in sequence if the trigger attribute is True.

+

Action

+

Attributes

+

An action as the same attribute as a command carried out by a button:

+
    +
  • instruction
  • +
  • delay
  • +
  • condition
  • +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Resources/Themes/index.html b/Resources/Themes/index.html new file mode 100644 index 00000000..a731cd76 --- /dev/null +++ b/Resources/Themes/index.html @@ -0,0 +1,2920 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Themes - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Rendering themes is a experimental features that allows a Cockpitdecks developer to provide different sets of default values.

+

Theme Declaration

+
cockpit-theme: night
+
+

Theme default value is an empty string (no theme).

+

If a theme is used, its name is prepended to attribute name:

+
	theme-default-label-color: purple
+
+

If no such attribute is found, the default-label-color attribute name is looked up (without the theme- prepended.)

+

Theme Usage

+

When a theme is defined, its name is prepended to some default values.

+

Default values affected by themes are:

+
    +
  • cockpit-color and cockpit-texture
  • +
  • All attributes that end with color. Example default-color.
  • +
  • All attributes that end with texture. Example annunciator-bg-texture.
  • +
  • All attributes that end with or contain the word font. Example label-font.
  • +
+
theme: light
+# Alternate theme is 'dark'
+default-label-color: white
+dark-default-label-color: coral
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Resources/index.html b/Resources/index.html new file mode 100644 index 00000000..913ead9c --- /dev/null +++ b/Resources/index.html @@ -0,0 +1,3075 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Resources - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

The following resources are common to all buttons.

+

Colors

+

Color can either be expressed by their name, as defined in the Python PILLOW package, or by a tuple of 3 or 4 values, representing red, green, blue, and optionally the transparency.

+

All values should be in the [0..255] range. For colors, 0 is black, 255 is pure color; for transparency, 0 is transparent, 255 is opaque.

+
    icon-color: (100, 40, 40, 200)
+    label-color: darkorange
+    text-color: mediumaquamarine
+
+

Named Colors

+

Named colors are defined at the highest aircraft level. They behave like a placeholder for a color, an alternate name.

+

Definition

+

In the main configuration file of the aircraft:

+
named-colors:
+	COCKPIT_BACKGROUND: skyblue
+	COCKPIT_NIGHT: slateblue
+	HIGHLIGHT: coral
+
+

Usage

+

In a button definition attributes:

+
label-color: HIGHLIGHT
+
+

Number Formatting

+

Cockpitdecks uses the same convention as python number to text formatting.

+

Fonts

+

Font files available to all decks are in the deckconfig/resources/fonts folder.

+

Font files must be either Truetype fonts (TTF) or Opentype fonts (OTF).

+

The name of the file is used to designate the font.

+

Fonts are loaded on start up.

+
	text-font: DIN Condensed Black.otf
+
+

Cockpitdecks comes with a few fonts appropriate for aeronautical use: Standard formal fonts like DIN, fancier fonts like LED fonts, icon fonts like font-awesome and weather-icon.

+

For Truetype font, it is not necessary to add the .ttf extension. For Opentype fonts it is necessary to add the .otf extension.

+
+

About font files.

+

Fonts placed in the resources/fonts folder are available for images on decks. They are not necessarily available as « web fonts » available in web decks.
+To be available directly, not through Cockpitdecks generated images, web decks fonts have to be made available in the asset folder and referenced in proper CSS files.

+
+

Cockpitdecks provides the following licensed fonts in its distribution:
+- D-DIN (4 variants),
+- Overpass, especially Overpass Mono,
+- Segment7Standard,
+- Split flaps Skyfont and SplitFlap-TV,
+- Icon fonts Font Awesome and weathericons.

+

The following fonts are recommended:
+- B612
+- DSEG: Original 7-segment and 14-segment fonts

+

Any Truetype or OpenType font can be added to Cockpitdecks.

+

Icons

+

Icon image files available to all decks need to be placed in the deckconfig/resources/icons folder.

+

Image files must be either JPEG images (JPG, JPEG) or Portable Network Graphic (PNG).

+

The name of the file is used to designate the icon.

+

Icons are loaded on start up and optionally cached.

+

Typical icon size should be (square) 128 to 256 pixels.

+

Internally, Cockpitdecks use 256 pixel icons. Icons are resized to the deck requested size upon display.

+
	icon: OFF_WHITE_FRAMED
+
+

Sounds

+

Sounds files can be provided very much like icons. Valid sound file formats are wav and mp3. Other sound file format may be added in the future, provided they can be played in a browser.

+

Documentation

+

Documentation of the aircraft decks can be placed in the folder deckconfig/docs or in the folder deckconfig/resources in a textual form (including markdown), or as PDF.

+

Decks

+

A particular aircraft allows for definition of particular decks. The deck definition is a special Yaml file that describes the deck capabilities and, optionally, for web decks, the layout of the buttons on the deck.

+

Please refer to the particular section on Deck Types for details.

+

Yaml

+

All configuration files are Yaml formatted. Yaml is simple, structured, and readable.

+

Yaml is loaded and interpreted by the python Yaml parser. Users must be aware that some keywords are sometimes interpreted by some parser. To prevent this interpretation, keywords should be placed between quotes.

+

For example:

+

variable: on

+

will be interpreted as boolean value true.

+

variable: "on"

+

will be interpreted as string on.

+

The following keywords have been noticeably discovered:

+

on, off, true, false, yes, no (all mapped to Boolean values True or False).

+

Cockpitdecks uses a Yaml 1.2 compliant parser (ruamel.yaml) that refuse those interpretation as describe in the Yaml 1.2 specifications.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Running/Adding a Page of Buttons to a Deck/index.html b/Running/Adding a Page of Buttons to a Deck/index.html new file mode 100644 index 00000000..2eb6334a --- /dev/null +++ b/Running/Adding a Page of Buttons to a Deck/index.html @@ -0,0 +1,3140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Adding a Page of Buttons to a Deck - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

This page is an attempt at providing all necessary step to create a new page of buttons to display on a deck.

+

We assume you installed Cockpitdecks properly, together with the Cockpitdecks Helper Plugin which is necessary to work in fully automatic mode.

+

Deck Preparation

+

First, make sure the deck model you own is a deck already available through Cockpitdecks. The following deck brands and models have been tested and used:

+
    +
  • Elgato Strem Deck: Streamdeck MK.2, Streamdeck XL, Streamdeck Mini, Streamdeck+, Streamdeck Neo
  • +
  • Loupedeck: LoupedeckLive, LoupedeckLive.s
  • +
  • Behringer: X-Touch Mini (I recently noticed that there are several version of the same device, but they should all work since the Makie mode is used.)
  • +
+

Prepare the Folder

+

In normal, simple, automatic mode, Cockpitdecks expects configuration to be found in the folder of the aircraft currently being used. (This may be adjusted by advanced configuration parameters if you do not want to store Cockpitdecks configuration files in the that folder.)

+

In the aircraft folder, create a folder named deckconfig.

+

Add the main config.yamlFile

+

Inside the above folder, create the main configuration file with this content. Adjust the content to your deck.

+
aircraft: Cessna 172 
+decks:
+  - name: Aviator Loupdeck
+    type: loupedeck
+    layout: cockpit
+    brightness: 70
+
+

Create a layout Folder

+

In the conifiguration file, you mention the name of the layout you are going to use for that deck: cockpit. Inside the deckconfig folder, create a sub-folder and name it cockpit.

+

Add a Page

+

Inside that cockpit folder, create a file index.yaml. Insert the following content in the file:

+
buttons:
+  - index: 0
+    label: Map
+    type: push
+    icon: map
+    command: sim/map/show_current
+
+

First Run

+

Start X-Plane, load the aircraft where you created your Cockpitdecks configuration folder.

+

Disconnect all software that access your deck.

+

Create an Environment File

+

If every runs on the same computer, you only need to supply X-Plane home directory. You can supply the folder either through a operating system environment variable SIMULATOR_HOME, or through the Cockpitdecks environment file. A myenviron.yaml file with the following content is sufficient:

+
SIMULATOR_NAME: X-Plane
+SIMULATOR_HOME: /Applications/X-Plane 12
+
+

Start Cockpitdecks

+
$ cockpitdecks-cli --env myenviron.yaml
+
+

Cockpitdecks will start immediately in demonstration mode, and connect to the simulator. Once it is connected to the simulator, it will try to collect the information about the aircraft that is currently loaded. This may take a few seconds. Once Cockpitdecks has the information about the aircraft currently being used, it will search for the deckconfig folder in the folder of the aircraft, and if found, will load the configuration it finds there.

+

If everything worked properly, you should now see the first, upper left button of your deck displaying the Map text. With no map icon.

+

Adding Custom Icons

+

Add buttons

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Running/Installation/index.html b/Running/Installation/index.html new file mode 100644 index 00000000..d12b2fc5 --- /dev/null +++ b/Running/Installation/index.html @@ -0,0 +1,3034 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Installation - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

installer.jpg

+

Cockpitdecks is a regular python application and will run under python 3.10, or newer.

+

It is recommended to create a dedicated environment and run Cockpitdecks within that environment.

+

X-Plane !

+

Cockpitdecks is a modern software that takes benefit from all available possibilities. While being developed, it is constantly updated to use the latest packages and features.

+

Therefore, Cockpitdecks will work better with the latest production release of X-Plane. It may not work properly, or some feature may not be available when using older versions of X-Plane. We do not have the engineering resources to maintain working versions of Cockpitdecks for all versions of X-Plane. While most features should still work in X-Plane 11, they were never tested, and we will not provide any support to make it work on older release.

+

Same occurs with aircrafts. Aircrafts are pieces of software that are regularly updated. It is a good practice to include the X-Plane and aircraft version information as comments in the deckconfig files, to precise which version of X-Plane or an aircraft is required to run properly.

+

As a practical example, X-Plane recently opened access to internal data through a new channel: A Web REST API access. This is offered in X-Plane release 12.1.1 or newer. Immediately, Cockpitdecks has taken benefit from this new, simplified mean to access simulator values.

+

It is good practice to maintain the software you use to the latest, production version. Cockpitdecks is no exception to this advise.

+

In particular, the X-Plane Cockpitdecks Helper plugin uses and requires the latest version of XPPython3 plugin to work. This plugin itself requires X-Plane 12 and recent version of X-Plane SDK. Cockpitdecks Helper plugin is strictly not required to run Cockpitdecks but some features will not work without it.

+

Enable X-Plane UDP

+
+

X-Plane 12 and UDP

+

X-Plane 12 appears to disable UDP networking on new installations. Check Settings -> Network page, and make sure “Accept incoming connections” is enabled.
+

+
+

(See X-Plane UDP manual. Will provide information here later.)

+

Install Cockpitdecks Application

+

Install Cockpitdecks Software

+

Create a new python environment and activate it. In that environment, issue the pip install command:

+
pip install 'cockpitdecks[xplane,weather,demoext,streamdeck] @ git+https://github.com/devleaks/cockpitdecks.git'
+
+

Cockpitdecks Extension Packages

+

Valid installable extension packages (between the [ ], comma separated, no space) are:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExtraContent
xplaneAdd X-Plane flight simulator. Mandatory if you want to use it with X-Plane flight simulator.
weatherAdd special iconic representation for weather. These representations fetch information outside of simulation software. That's why it is not bundled with Cockpitdecks. Recommended.
streamdeckFor Elgato Stream Deck devices
loupedeckFor Loupedeck LoupedeckLive, LoupedeckLive.s and Loupedeck CT devices
xtouchminiFor Berhinger X-Touch Mini devices
demoextAdd a few Loupedeck and Stream Deck+ demo extensions. Recommended.
developmentFor Cockpitdecks developer only, adds testing packages and python types. Useless if you do not develop Cockpitdecks software.
+

Install Cockpitdecks Helper Plugin

+
+

Cockpitdecks X-Plane Helper Plugin

+

You can do this step later, but some functions will not work or be available inside Cockpitdecks.

+
+

Cockpitdecks Helper Plugin is written in the python language. So it needs the XPPython3 X-Plane plugin installed. XPPython3 plugin allow for execution of python code inside X-Plane.

+

Cockpitdecks XPPython3 plugin is located in the

+
< Cockpitdecks-installed-code > /cockpitdecks/resources/xppython3-plugins
+
+

folder in the source code. There is a single file.

+

To install, copy the plugin file to:

+
... /X-Plane 12/resources/plugins/PythonPlugins/PI_cockpitdecks.py
+
+

and ask XPPython3 plugin to reload its scripts.

+

Install Aircraft Specific deckconfig Folders

+

Duane, a Cockpitdecks aficionado has realized several configurations for several aircrafts.

+

You can find them here, download them and install them in your aircraft folder.

+

Cockpitdecks deckconfig folder must be placed in the folder of the X-Plane aircraft to be found by Cockpitdecks or the Cockpitdecks Helper plugin.

+
<X-Plane 12 Folder>/Aircraft/Extra Airicraft/Toliss A321/deckconfig
+
+

Now you are ready to start Cockpitdecks.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Running/Usage/index.html b/Running/Usage/index.html new file mode 100644 index 00000000..679fee72 --- /dev/null +++ b/Running/Usage/index.html @@ -0,0 +1,3033 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Usage - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

installer.jpg

+

Disconnect OEM Applications

+

First, you have to completely stop (quit completely) original manufacturer deck configuration applications. They take exclusive access to the device and that prevents Cockpitdecks from finding and using them.

+

Adjust environment.yaml

+

Cockpitdecks uses a configuration file, called the Cockpitdecks environment file, to define a few elements that cannot easily be guessed. Cockpitdecks provides a environment file that is suitable for single computer installation. You must, however, adjust at least the simulator folder path.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableDefiniton
SIMULATOR_NAMEName of simulator to use. Currently, only X-Plane is a valid value.
SIMULATOR_HOMEHome directory of X-Plane on the computer where Cockpitdecks runs. If X-Plane is installed on a remote host, SIMULATOR_HOME must be None.
SIMULATOR_HOSTHostname or IP address where X-Plane runs. Defaults to local host.
APP_HOSTTuple (Hostname or IP address, port) where Cockpitdecks application runs. If specified through operating system environment variables, use (APP_HOST and APP_PORT). Default to (localhost, 7777).
API_PORTX-Plane (12.1.1 and above) where REST API runs. Default to 8086.
API_PATHX-Plane (12.1.1 and above) where REST API: X-Plane API root path. Currently default to /api/v1.
COCKPITDECKS_EXTENSION_PATHList of paths where to search for Cockpitdecks extensions
COCKPITDECKS_EXTENSION_NAMEList of python package names that contains Cockpitdecks extensions
+

In addition, there is a operating system environment variable COCKPITDECKS_PATH that lists folder paths where Cockpitdecks will look for aircraft configurations.

+

If you do not want to modify the Cockpitdecks-provided environ.yaml file, you always can supply one on the command line with the —-env flag.

+

The set of global parameters provided in the environ.yaml file is called the Cockpitdecks environment, since it provides all « external » information to Cockpitdecks (external, relative to Cockpitdecks).

+

Cockpitdecks provides two templates configuration files for local and remote use.

+

The minimal environment file if everything runs locally is

+
SIMILATOR_NAME: X-Plane
+SIMULATOR_HOME: <path to where X-Plane software is installed>
+
+

Start Cockpitdecks

+

Currently, Cockpitdecks only offers a command-line interface to be executed in a shell window.

+

Installation of the above package in a python environment adds a single command cockpitdecks-cli that can be used to start Cockpitdecks and provide options.

+

Command-Line Help

+

If always is a good idea to see what the client application offers:

+
$ cockpitdecks-cli --help
+usage: cockpitdecks-cli [-h] [-e environ_file] [-d] [-f] [-v] [aircraft_folder]
+
+Start Cockpitdecks
+
+positional arguments:
+  aircraft_folder       aircraft folder for non automatic start
+
+options:
+  -h, --help            show this help message and exit
+  -d, --demo            start demo mode
+  -e environ_file, --env environ_file
+                        alternate environment file
+  -f, --fixed           does not automatically switch aircraft
+  -v, --verbose         show startup information
+
+

Test Cockpitdecks In Demonstration Mode

+

A second easy step is to start Cockpitdecks in demonstration mode. In this mode, it will offer a single demonstration web deck. See Examples for details about the demonstration.

+
cockpitdecks-cli --demo
+
+

In this mode, Cockpitdecks will start a single web deck. Head for the index web page as specified in the environ.yaml environment file (APP_HOST).

+

demo-deck-list.png

+

demo-deck.png

+
+

NoSimulator

+

If no simulator package is loaded, Cockpitdecks will start a dummy, empty simulator connector for its internal use. The above demonstration will be fully functional. Please note that if no specific deck package has been loaded, "hardware" reprensentation may not appear correctly on the emulated web decks.

+
+

Normal Operations

+

There are two possible ways of working with Cockpitdecks:

+
    +
  1. Either Cockpitdecks runs on the same computer as X-Plane,
  2. +
  3. or Cockpitdecks runs on a remote computer on the same local area network.
  4. +
+
+

Note

+

Cockpitdecks provides two templates global parameter/environment files to get you started.

+
+

Cockpitdecks and X-Plane Run On Same Computer

+

This is the simplest case. In this mode, everything is automatic.

+

Make sure the configuration file is setup. When everything is run on the same computer, the only important parameter is the X-Plane home folder that should be supplied in SIMULATOR_HOME configuration variable. Alternatively, for convenience, the operating system SIMULATOR_HOME environment variable can be set, especially if it is the only configuration variable that has to be changed. Then issue:

+
cockpitdecks-cli
+
+

If you defined a personal environment file:

+
cockpitdecks-cli --env myenv.yaml
+
+

Cockpitdecks will immediately start in demonstration mode and listen for X-Plane interaction. If Cockpitdecks detects that X-Plane is running, finds that an aircraft is loaded, and that a Cockpitdecks deckconfig folder exists in the folder of that aircraft, Cockpitdecks will load that configuration.

+

If no configuration is found, Cockpitdecks will listen and interpret X-Plane data but will not load a new configuration.

+

This mode is fully automatic. If X-Plane is stopped or later restarted, Cockpitdecks will notice it and either wait idle that X-Plane is started, and if X-Plane is running, Cockpitdecks will attempt to load the aircraft configuration, if any.

+

Cockpitdecks always attempts to load the current aircraft deckconfig configuration, if present. Similarly, the XPPython3 plugin, if installed, will load the corresponding new configuration as well.

+

Forced Configuration

+

May be thhe aircraft loaded in X-Plane has no Cockpitdecks configuration but still, a configuration need to be loaded. The command-line interface can help:

+
cockpitdecks-cli --env myenv.yaml folder-where-there-is-a-config --fixed
+
+

The above command will look for a deckconfig folder in the folder supplied on the command line. The --fixed flag indicates that Cockpitdecks should not try to load another configuration in case the folder loaded (the one supplied on the command line) and the folder of the aircraft used in the simulator do not match.

+

Cockpidecks and X-Plane Run on Different Computer

+

In this case, it is not possible for Cockpitdecks to locate aircraft configuration files since they probably are on the remote computer. A set of configuration file will need to be supplied to Cockpitdecks on the command line to start with. (If no folder is supplied, Cockpitdecks will start in demonstration mode. See above.)

+
cockpitdecks-cli aircrafts/A21N --fixed
+
+

Cockpitdecks will start and load the configuration in aircrafts/A21N/deckconfig, then Cockpitdecks will attempt to connect to X-Plane.

+

The optional --fixed flag ensure that Cockpitdecks will not reload a new aircraft if it detects the aircraft in X-Plane has changed or does not correspond to the aircraft currently being used.

+

If --fixed is not present, Cockpitdecks will attempt to find a suitable configuration to load. To do this, Cockpitdecks will search for a folder named like the X-Plane aircraft folder. It will search in all folders listed in the COCKPITDECKS_PATH python or environment variable.

+

Here is an example. Suppose you loaded Toliss A321 aircraft in X-Plane. The folder name for that aircraft is Toliss A321.

+

If you defined the python variable as such:

+
COCKPITDECKS_PATH="/XPDATA/CockpitdecksConfig:/Volume0/XPlane/Cockpitdecks/data
+
+

Cockpitdecks will search in each of the above folders (/XPDATA/CockpitdecksConfig and /Volume0/XPlane/Cockpitdecks/data) for a folder named Toliss A321. It will then check whether that folder contains a deckconfig subfolder. If it does, Cockpitdecks will load that configuration. If the folder /XPDATA/CockpitdecksConfig/Toliss A321/deckconfig is found, Cockpitdecks will load it.

+

In all case, Cockpitdecks will repetitively try to connect to X-Plane. If it fails to connect, it infinitely tries again until it succeeds. If not connected, decks will load but no command will be issued to X-Plane and no data will come from X-Plane to update decks.

+

When Cockpitdecks successfully connects to X-Plane, it refreshes all pages by reloading them to reflect the state of the aircraft on the decks.

+

If Cockpitdecks fails to connect to X-Plane or notices it does no longer receive dataref values from X-Plane, it will again repetitively try to connect to it until it succeeds.

+

Troubleshooting

+

To report an issue with Cockpitdecks, you should always include the XPPython3.log file created in the X-Plane folder. Cockpitdecks also create a cockpitdecks.log files with more information in the directory you started Cockpitdecks from.

+

The level of information produced in the file is controlled by the logging level parameter. (info=some information and warnings, debug=a lot of information for debugging purpose, your XPPython3.log file may grow quite large.) The parameter is available at the global plugin level (the entire plugin will report all messages), or can be set at a Cockpitdecks internal module level to pin point issues.

+

Cockpitdecks is stateless. If we except its internal statistics, Cockpitdecks does not maintain any state variable of a situation. Therefore, at any time, it is possible to stop and restart it. Should a problem persists, please file an issue on github with necessary information to reproduce it.

+

Termination

+

To terminate Cockpitdecks, press a single Ctrl+C to stop Cockpitdecks client application.

+

Cockpitdecks is designed to terminates cleanly. All requested datarefs monitoring are cancelled, connections are closed, all threads are terminated and joined cleanly. However, it may sometimes take a few seconds before a thread terminates. For example, if a thread is meant to run every 30 seconds, it may be necessary to wait a full 30 seconds before the thread notices the termination request and quits. Longer threads (above 30 seconds or a minute) check periodically for termination request. Cockpitdecks should always terminate cleanly to release resources it controls from the simulator.

+

If necessary, there is an activation that can be assigned to a button to stop Cockpitdecks.

+

If necessary, or blocked, pressing Ctrl+C several time in the main window will stop Cockpitdecks completely right away.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Simulator/index.html b/Simulator/index.html new file mode 100644 index 00000000..2470a6d0 --- /dev/null +++ b/Simulator/index.html @@ -0,0 +1,2907 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Simulator - Cockpitdecks Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + +
+ +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

The Simulator entity represent the simulator software and contains all procedures necessary for interaction with the flight simulation software.

+

There currently is only one type of flight simulation software supported and it is Laminar Research X-Plane (release 12 onwards).

+

It is possible to create a new Simulator implementation for another software, like for example Microsoft Flight Simulator.

+

In addition to the core simulation software, the Simulator also proposes a few entities that represent interactions with it.

+

Simulator Data Values

+

The simulation software presents to Cockpitdecks all its reachable « values ». Any value that is accessible in the simulation software is made available to Cockpitdecks through the SimulationData. A sophisticated mechanism updates the data in Cockpitdecks every time the data is modified in the simulator.

+

For example, Laminar X-Plane extensively uses « datarefs », while Microsoft Flight Simulator uses « simvars ». They both are values coming from the simulator, and they both can be used in Cockpitdecks.

+

Instructions

+

Cockpitdecks offers the Instruction, an action that must be carried out in the simulator software, provided that the simulation software allows for « external action » to be accepted.

+

Again, as an example, Laminar X-Plane has « commands » (offered in two modes of operation), and Microsoft Flight Simulator has « idontknow ».

+

Simulator Core

+

The Simulator entity is responsible for maintaining a connection to the Simulator software to collect simulator data values and issue instructions.

+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/css/dracula-colors.css b/assets/css/dracula-colors.css new file mode 100644 index 00000000..96ddf45c --- /dev/null +++ b/assets/css/dracula-colors.css @@ -0,0 +1,60 @@ +/* Devleaks personal preference for heading and emphasis + * (matches some Obsidian Dracula Theme at https://draculatheme.com/contribute) + * + * Last updated: 25-MAR-2024 + * + */ +.md-typeset strong { + color: RGB(255, 184, 108); + font-weight: lighter; +} +.md-typeset em { + color: RGB(80, 250, 123); +} +.md-typeset i { + color: RGB(80, 250, 123); +} +.md-typeset b { + font-weight: bolder; + color: rgb(255, 202, 128); +} +.md-typeset del { + color: rgb(255, 149, 128); +} +.md-typeset mark { + background-color: RGBA(139, 233, 253, 0.8); + color: RGB(68, 71, 90); +} +.md-typeset code { + color: RGB(255, 184, 108) !important; +} +.md-typeset h1 { + font-variation-settings: "wght" 300,"slnt" 0,"SRFF" 0.7; + color: RGB(189, 147, 249); +} +.md-typeset h2 { + font-variation-settings: "wght" 300,"slnt" 0,"SRFF" 0.7; + color: RGB(255, 184, 108); +} +.md-typeset h3 { + font-variation-settings: "wght" 300,"slnt" 0,"SRFF" 0.7; + color: RGB(80, 250, 123); +} +.md-typeset h4 { + font-variation-settings: "wght" 250,"slnt" -8,"SRFF" 0.7; + font-size: larger; + color: RGB(255, 85, 85); +} +.md-typeset h5 { + font-variation-settings: "wght" 250,"slnt" -10,"SRFF" 0.4; + font-size: larger; + color: RGB(241, 250, 140) !important; +} +.md-typeset h6 { + font-variation-settings: "wght" 200,"slnt" -10,"SRFF" 0.4; + font-size: larger; + color: RGB(139, 233, 253); +} +.md-footer-copyright { + font-size: x-small; +} \ No newline at end of file diff --git a/assets/css/extra.css b/assets/css/extra.css new file mode 100644 index 00000000..e785a0eb --- /dev/null +++ b/assets/css/extra.css @@ -0,0 +1 @@ +@charset "UTF-8";:root>*{--md-code-link-bg-color:hsla(0, 0%, 96%, 1);--md-code-link-accent-bg-color:var(--md-code-link-bg-color);--md-default-bg-color--trans:rgb(100%, 100%, 100%, 0);--md-code-title-bg-color:var(--md-code-bg-color);--md-code-inline-bg-color:var(--md-code-bg-color);--md-code-special-bg-color:#e8e8e8;--md-code-alternate-bg-color:var(--md-code-bg-color);--md-code-hl-punctuation-color:var(--md-code-fg-color);--md-code-hl-namespace-color:var(--md-code-fg-color);--md-code-hl-entity-color:var(--md-code-hl-keyword-color);--md-code-hl-tag-color:var(--md-code-hl-keyword-color);--md-code-hl-builtin-color:var(--md-code-hl-constant-color);--md-code-hl-class-color:var(--md-code-hl-function-color);--md-typeset-a-color:#00bcd4;--md-progress-stripe:var(--md-default-bg-color--lighter);--md-progress-100:#00e676;--md-progress-80:#00e676;--md-progress-60:#fbc02d;--md-progress-40:#ff9100;--md-progress-20:#ff5252;--md-progress-0:#ff1744;--md-typeset-kbd-color:#ebebeb;--md-typeset-kbd-border-color:#b8b8b8;--md-typeset-kbd-accent-color:hsla(0, 100%, 100%, 1)}:root>[data-md-color-scheme=slate]{--md-code-link-bg-color:hsla(232, 15%, 15%, 1);--md-code-link-accent-bg-color:var(--md-code-link-bg-color);--md-code-special-bg-color:#2b2d3b;--md-default-bg-color--trans:hsla(232,15%,15%, 0);--md-typeset-kbd-color:var(--md-default-fg-color--lightest);--md-typeset-kbd-border-color:#1a1c24;--md-typeset-kbd-accent-color:var(--md-default-fg-color--lighter)}:root>[data-md-color-scheme=dracula]{--md-default-fg-color:rgba(248, 248, 242, 0.87);--md-default-fg-color--light:rgba(248, 248, 242, 0.54);--md-default-fg-color--lighter:rgba(248, 248, 242, 0.16);--md-default-fg-color--lightest:rgba(248, 248, 242, 0.07);--md-default-autocomplete-fg-color:rgba(248, 248, 242, 0.4);--md-shadow-z2:0 0.2rem 0.5rem hsla(0, 0%, 0%, 0.3),0 0 0.05rem hsla(0, 0%, 0%, 0.2);--md-default-bg-color:var(--md-default-bg-color--darkest);--md-default-bg-color--light:rgba(50, 52, 67, 0.7);--md-default-bg-color--lighter:rgba(50, 52, 67, 0.3);--md-default-bg-color--lightest:rgba(50, 52, 67, 0.12);--md-default-bg-color--trans:rgba(50, 52, 67, 0);--md-default-bg-color--dark:#2b2e3b;--md-default-bg-color--darker:#252732;--md-default-bg-color--darkest:#1e2029;--md-default-bg-color--ultra-dark:#111217;--md-text-color:var(--md-default-fg-color);--md-typeset-color:var(--md-default-fg-color);--md-admonition-fg-color:var(--md-default-fg-color);--md-code-fg-color:hsl(60, 30%, 96%);--md-code-bg-color:hsl(231, 15%, 18%);--md-code-title-bg-color:var(--md-default-bg-color--ultra-dark);--md-code-inline-bg-color:#323443;--md-code-hl-operator-color:hsl(326, 100%, 74%);--md-code-hl-punctuation-color:hsl(60, 30%, 96%);--md-code-hl-string-color:hsl(65, 92%, 76%);--md-code-hl-special-color:hsl(265, 89%, 78%);--md-code-hl-number-color:hsl(265, 89%, 78%);--md-code-hl-keyword-color:hsl(326, 100%, 74%);--md-code-hl-name-color:hsl(60, 30%, 96%);--md-code-hl-constant-color:hsl(265, 89%, 78%);--md-code-hl-function-color:hsl(135, 94%, 65%);--md-code-hl-comment-color:hsl(225, 27%, 51%);--md-code-hl-variable-color:hsl(31, 100%, 71%);--md-code-hl-generic-color:hsl(225, 27%, 51%);--md-code-hl-color:hsl(231, 25%, 25%);--md-code-hl-entity-color:hsl(135, 94%, 65%);--md-code-hl-tag-color:hsl(326, 100%, 74%);--md-code-hl-namespace-color:hsl(60, 30%, 96%);--md-code-hl-builtin-color:hsl(191, 97%, 77%);--md-code-hl-class-color:hsl(191, 97%, 77%);--md-code-special-bg-color:#1c1e26;--md-code-alternate-bg-color:#3d3e49;--md-code-link-bg-color:#364653;--md-typeset-a-color:hsl(191, 97%, 77%);--md-typeset-mark-color:#6e7252;--md-typeset-del-color:#734568;--md-typeset-ins-color:#36724e;--md-progress-stripe:var(--md-default-bg-color--lightest);--md-progress-100:hsl(135, 94%, 65%);--md-progress-80:hsl(135, 92%, 79%);--md-progress-60:hsl(65, 92%, 76%);--md-progress-40:hsl(31, 100%, 71%);--md-progress-20:hsl(326, 100%, 74%);--md-progress-0:hsl(0, 100%, 67%);--md-typeset-kbd-color:var(--md-default-fg-color--lightest);--md-typeset-kbd-border-color:var(--md-default-bg-color--ultra-dark);--md-typeset-kbd-accent-color:var(--md-default-fg-color--lighter)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=red],[data-md-color-scheme=dracula][data-md-color-primary=red]{--md-primary-code-bg-color:#47303a;--md-primary-fg-color:hsla(0deg, 100%, 67%, 1);--md-primary-fg-color--transparent:hsla(0deg, 100%, 67%, 0.1);--md-primary-fg-color--light:hsla(0deg, 100%, 72%, 1);--md-primary-fg-color--dark:hsla(0deg, 100%, 62%, 1);--md-primary-bg-color:var(--md-default-bg-color);--md-primary-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=pink],[data-md-color-scheme=dracula][data-md-color-primary=pink]{--md-primary-code-bg-color:#47354b;--md-primary-fg-color:hsla(326deg, 100%, 74%, 1);--md-primary-fg-color--transparent:hsla(326deg, 100%, 74%, 0.1);--md-primary-fg-color--light:hsla(326deg, 100%, 79%, 1);--md-primary-fg-color--dark:hsla(326deg, 100%, 69%, 1);--md-primary-bg-color:var(--md-default-bg-color);--md-primary-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=purple],[data-md-color-scheme=dracula][data-md-color-primary=purple]{--md-primary-code-bg-color:#3e3952;--md-primary-fg-color:hsla(265deg, 89%, 78%, 1);--md-primary-fg-color--transparent:hsla(265deg, 89%, 78%, 0.1);--md-primary-fg-color--light:hsla(265deg, 89%, 83%, 1);--md-primary-fg-color--dark:hsla(265deg, 89%, 73%, 1);--md-primary-bg-color:var(--md-default-bg-color);--md-primary-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=deep-purple],[data-md-color-scheme=dracula][data-md-color-primary=deep-purple]{--md-primary-code-bg-color:#3e3952;--md-primary-fg-color:hsla(265deg, 89%, 78%, 1);--md-primary-fg-color--transparent:hsla(265deg, 89%, 78%, 0.1);--md-primary-fg-color--light:hsla(265deg, 89%, 83%, 1);--md-primary-fg-color--dark:hsla(265deg, 89%, 73%, 1);--md-primary-bg-color:var(--md-default-bg-color);--md-primary-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=blue],[data-md-color-scheme=dracula][data-md-color-primary=blue]{--md-primary-code-bg-color:#303446;--md-primary-fg-color:hsla(225deg, 27%, 51%, 1);--md-primary-fg-color--transparent:hsla(225deg, 27%, 51%, 0.1);--md-primary-fg-color--light:hsla(225deg, 27%, 56%, 1);--md-primary-fg-color--dark:hsla(225deg, 27%, 46%, 1);--md-primary-bg-color:var(--md-default-bg-color);--md-primary-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=indigo],[data-md-color-scheme=dracula][data-md-color-primary=indigo]{--md-primary-code-bg-color:#303446;--md-primary-fg-color:hsla(225deg, 27%, 51%, 1);--md-primary-fg-color--transparent:hsla(225deg, 27%, 51%, 0.1);--md-primary-fg-color--light:hsla(225deg, 27%, 56%, 1);--md-primary-fg-color--dark:hsla(225deg, 27%, 46%, 1);--md-primary-bg-color:var(--md-default-bg-color);--md-primary-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=light-blue],[data-md-color-scheme=dracula][data-md-color-primary=light-blue]{--md-primary-code-bg-color:#303446;--md-primary-fg-color:hsla(225deg, 27%, 51%, 1);--md-primary-fg-color--transparent:hsla(225deg, 27%, 51%, 0.1);--md-primary-fg-color--light:hsla(225deg, 27%, 56%, 1);--md-primary-fg-color--dark:hsla(225deg, 27%, 46%, 1);--md-primary-bg-color:var(--md-default-bg-color);--md-primary-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=cyan],[data-md-color-scheme=dracula][data-md-color-primary=cyan]{--md-primary-code-bg-color:#364653;--md-primary-fg-color:hsla(191deg, 97%, 77%, 1);--md-primary-fg-color--transparent:hsla(191deg, 97%, 77%, 0.1);--md-primary-fg-color--light:hsla(191deg, 97%, 82%, 1);--md-primary-fg-color--dark:hsla(191deg, 97%, 72%, 1);--md-primary-bg-color:var(--md-default-bg-color);--md-primary-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=teal],[data-md-color-scheme=dracula][data-md-color-primary=teal]{--md-primary-code-bg-color:#364653;--md-primary-fg-color:hsla(191deg, 97%, 77%, 1);--md-primary-fg-color--transparent:hsla(191deg, 97%, 77%, 0.1);--md-primary-fg-color--light:hsla(191deg, 97%, 82%, 1);--md-primary-fg-color--dark:hsla(191deg, 97%, 72%, 1);--md-primary-bg-color:var(--md-default-bg-color);--md-primary-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=green],[data-md-color-scheme=dracula][data-md-color-primary=green]{--md-primary-code-bg-color:#2d4840;--md-primary-fg-color:hsla(135deg, 94%, 65%, 1);--md-primary-fg-color--transparent:hsla(135deg, 94%, 65%, 0.1);--md-primary-fg-color--light:hsla(135deg, 94%, 70%, 1);--md-primary-fg-color--dark:hsla(135deg, 94%, 60%, 1);--md-primary-bg-color:var(--md-default-bg-color);--md-primary-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=light-green],[data-md-color-scheme=dracula][data-md-color-primary=light-green]{--md-primary-code-bg-color:#2d4840;--md-primary-fg-color:hsla(135deg, 94%, 65%, 1);--md-primary-fg-color--transparent:hsla(135deg, 94%, 65%, 0.1);--md-primary-fg-color--light:hsla(135deg, 94%, 70%, 1);--md-primary-fg-color--dark:hsla(135deg, 94%, 60%, 1);--md-primary-bg-color:var(--md-default-bg-color);--md-primary-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=lime],[data-md-color-scheme=dracula][data-md-color-primary=lime]{--md-primary-code-bg-color:#2d4840;--md-primary-fg-color:hsla(135deg, 94%, 65%, 1);--md-primary-fg-color--transparent:hsla(135deg, 94%, 65%, 0.1);--md-primary-fg-color--light:hsla(135deg, 94%, 70%, 1);--md-primary-fg-color--dark:hsla(135deg, 94%, 60%, 1);--md-primary-bg-color:var(--md-default-bg-color);--md-primary-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=yellow],[data-md-color-scheme=dracula][data-md-color-primary=yellow]{--md-primary-code-bg-color:#454842;--md-primary-fg-color:hsla(65deg, 92%, 76%, 1);--md-primary-fg-color--transparent:hsla(65deg, 92%, 76%, 0.1);--md-primary-fg-color--light:hsla(65deg, 92%, 81%, 1);--md-primary-fg-color--dark:hsla(65deg, 92%, 71%, 1);--md-primary-bg-color:var(--md-default-bg-color);--md-primary-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=amber],[data-md-color-scheme=dracula][data-md-color-primary=amber]{--md-primary-code-bg-color:#454842;--md-primary-fg-color:hsla(65deg, 92%, 76%, 1);--md-primary-fg-color--transparent:hsla(65deg, 92%, 76%, 0.1);--md-primary-fg-color--light:hsla(65deg, 92%, 81%, 1);--md-primary-fg-color--dark:hsla(65deg, 92%, 71%, 1);--md-primary-bg-color:var(--md-default-bg-color);--md-primary-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=orange],[data-md-color-scheme=dracula][data-md-color-primary=orange]{--md-primary-code-bg-color:#473e3d;--md-primary-fg-color:hsla(31deg, 100%, 71%, 1);--md-primary-fg-color--transparent:hsla(31deg, 100%, 71%, 0.1);--md-primary-fg-color--light:hsla(31deg, 100%, 76%, 1);--md-primary-fg-color--dark:hsla(31deg, 100%, 66%, 1);--md-primary-bg-color:var(--md-default-bg-color);--md-primary-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=deep-orange],[data-md-color-scheme=dracula][data-md-color-primary=deep-orange]{--md-primary-code-bg-color:#473e3d;--md-primary-fg-color:hsla(31deg, 100%, 71%, 1);--md-primary-fg-color--transparent:hsla(31deg, 100%, 71%, 0.1);--md-primary-fg-color--light:hsla(31deg, 100%, 76%, 1);--md-primary-fg-color--dark:hsla(31deg, 100%, 66%, 1);--md-primary-bg-color:var(--md-default-bg-color);--md-primary-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=red],[data-md-color-scheme=dracula][data-md-color-accent=red]{--md-code-link-accent-bg-color:#472c36;--md-accent-fg-color:hsla(0deg, 100%, 62%, 1);--md-accent-fg-color--transparent:hsla(0deg, 100%, 62%, 0.1);--md-accent-bg-color:var(--md-default-bg-color);--md-accent-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=pink],[data-md-color-scheme=dracula][data-md-color-accent=pink]{--md-code-link-accent-bg-color:#473149;--md-accent-fg-color:hsla(326deg, 100%, 69%, 1);--md-accent-fg-color--transparent:hsla(326deg, 100%, 69%, 0.1);--md-accent-bg-color:var(--md-default-bg-color);--md-accent-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=purple],[data-md-color-scheme=dracula][data-md-color-accent=purple]{--md-code-link-accent-bg-color:#3c3652;--md-accent-fg-color:hsla(265deg, 89%, 73%, 1);--md-accent-fg-color--transparent:hsla(265deg, 89%, 73%, 0.1);--md-accent-bg-color:var(--md-default-bg-color);--md-accent-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=deep-purple],[data-md-color-scheme=dracula][data-md-color-accent=deep-purple]{--md-code-link-accent-bg-color:#3c3652;--md-accent-fg-color:hsla(265deg, 89%, 73%, 1);--md-accent-fg-color--transparent:hsla(265deg, 89%, 73%, 0.1);--md-accent-bg-color:var(--md-default-bg-color);--md-accent-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=blue],[data-md-color-scheme=dracula][data-md-color-accent=blue]{--md-code-link-accent-bg-color:#2e3243;--md-accent-fg-color:hsla(225deg, 27%, 46%, 1);--md-accent-fg-color--transparent:hsla(225deg, 27%, 46%, 0.1);--md-accent-bg-color:var(--md-default-bg-color);--md-accent-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=indigo],[data-md-color-scheme=dracula][data-md-color-accent=indigo]{--md-code-link-accent-bg-color:#2e3243;--md-accent-fg-color:hsla(225deg, 27%, 46%, 1);--md-accent-fg-color--transparent:hsla(225deg, 27%, 46%, 0.1);--md-accent-bg-color:var(--md-default-bg-color);--md-accent-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=light-blue],[data-md-color-scheme=dracula][data-md-color-accent=light-blue]{--md-code-link-accent-bg-color:#2e3243;--md-accent-fg-color:hsla(225deg, 27%, 46%, 1);--md-accent-fg-color--transparent:hsla(225deg, 27%, 46%, 0.1);--md-accent-bg-color:var(--md-default-bg-color);--md-accent-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=cyan],[data-md-color-scheme=dracula][data-md-color-accent=cyan]{--md-code-link-accent-bg-color:#324553;--md-accent-fg-color:hsla(191deg, 97%, 72%, 1);--md-accent-fg-color--transparent:hsla(191deg, 97%, 72%, 0.1);--md-accent-bg-color:var(--md-default-bg-color);--md-accent-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=teal],[data-md-color-scheme=dracula][data-md-color-accent=teal]{--md-code-link-accent-bg-color:#324553;--md-accent-fg-color:hsla(191deg, 97%, 72%, 1);--md-accent-fg-color--transparent:hsla(191deg, 97%, 72%, 0.1);--md-accent-bg-color:var(--md-default-bg-color);--md-accent-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=green],[data-md-color-scheme=dracula][data-md-color-accent=green]{--md-code-link-accent-bg-color:#2a483d;--md-accent-fg-color:hsla(135deg, 94%, 60%, 1);--md-accent-fg-color--transparent:hsla(135deg, 94%, 60%, 0.1);--md-accent-bg-color:var(--md-default-bg-color);--md-accent-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=light-green],[data-md-color-scheme=dracula][data-md-color-accent=light-green]{--md-code-link-accent-bg-color:#2a483d;--md-accent-fg-color:hsla(135deg, 94%, 60%, 1);--md-accent-fg-color--transparent:hsla(135deg, 94%, 60%, 0.1);--md-accent-bg-color:var(--md-default-bg-color);--md-accent-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=lime],[data-md-color-scheme=dracula][data-md-color-accent=lime]{--md-code-link-accent-bg-color:#2a483d;--md-accent-fg-color:hsla(135deg, 94%, 60%, 1);--md-accent-fg-color--transparent:hsla(135deg, 94%, 60%, 0.1);--md-accent-bg-color:var(--md-default-bg-color);--md-accent-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=yellow],[data-md-color-scheme=dracula][data-md-color-accent=yellow]{--md-code-link-accent-bg-color:#45483e;--md-accent-fg-color:hsla(65deg, 92%, 71%, 1);--md-accent-fg-color--transparent:hsla(65deg, 92%, 71%, 0.1);--md-accent-bg-color:var(--md-default-bg-color);--md-accent-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=amber],[data-md-color-scheme=dracula][data-md-color-accent=amber]{--md-code-link-accent-bg-color:#45483e;--md-accent-fg-color:hsla(65deg, 92%, 71%, 1);--md-accent-fg-color--transparent:hsla(65deg, 92%, 71%, 0.1);--md-accent-bg-color:var(--md-default-bg-color);--md-accent-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=orange],[data-md-color-scheme=dracula][data-md-color-accent=orange]{--md-code-link-accent-bg-color:#473d39;--md-accent-fg-color:hsla(31deg, 100%, 66%, 1);--md-accent-fg-color--transparent:hsla(31deg, 100%, 66%, 0.1);--md-accent-bg-color:var(--md-default-bg-color);--md-accent-bg-color--light:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] :not([data-md-color-scheme])[data-md-color-primary=deep-orange],[data-md-color-scheme=dracula][data-md-color-accent=deep-orange]{--md-code-link-accent-bg-color:#473d39;--md-accent-fg-color:hsla(31deg, 100%, 66%, 1);--md-accent-fg-color--transparent:hsla(31deg, 100%, 66%, 0.1);--md-accent-bg-color:var(--md-default-bg-color);--md-accent-bg-color--light:var(--md-default-bg-color--light)}:root{--md-heart:#ff5252;--md-heart-big:#ff1744}:root :focus-visible{outline-style:solid}:root [data-md-color-scheme=dracula]{--md-heart:hsl(326, 100%, 74%);--md-heart-big:hsl(0, 100%, 67%)}.md-typeset h4{margin:2em 0 1em}.md-typeset a.source-link{position:relative;top:-.6rem;float:right;color:var(--md-default-fg-color--lighter);transition:color 125ms}.md-typeset a.source-link:hover{color:var(--md-accent-fg-color)}.md-typeset a.source-link .twemoji{height:1.2rem}.md-typeset a.source-link .twemoji svg{width:1.2rem;height:1.2rem}.md-typeset div.highlight.md-max-height pre>code{max-height:15rem}.twemoji.heart-throb svg,.twemoji.heart-throb-hover svg{position:relative;color:var(--md-heart);animation:pulse 1.5s ease infinite}@keyframes pulse{0%{transform:scale(1)}40%{color:var(--md-heart-big);transform:scale(1.3)}50%{transform:scale(1.2)}60%{color:var(--md-heart-big);transform:scale(1.3)}100%{transform:scale(1)}}footer.sponsorship{text-align:center}footer.sponsorship hr{display:inline-block;width:1.6rem;margin:0 .7rem;vertical-align:middle;border-bottom:2px solid var(--md-default-fg-color--lighter)}footer.sponsorship:hover hr{border-color:var(--md-accent-fg-color)}footer.sponsorship:not(:hover) .twemoji.heart-throb-hover svg{color:var(--md-default-fg-color--lighter)!important}body:not([data-md-prefers-color-scheme=true])[data-md-color-scheme=dracula] .md-icon .light-mode,body:not([data-md-prefers-color-scheme=true])[data-md-color-scheme=dracula] .md-icon .system-mode,body:not([data-md-prefers-color-scheme=true])[data-md-color-scheme=dracula] .md-icon .unknown-mode{display:none}body:not([data-md-prefers-color-scheme=true])[data-md-color-scheme=default] .md-icon .dark-mode,body:not([data-md-prefers-color-scheme=true])[data-md-color-scheme=default] .md-icon .system-mode,body:not([data-md-prefers-color-scheme=true])[data-md-color-scheme=default] .md-icon .unknown-mode{display:none}body:not([data-md-prefers-color-scheme=true]):not([data-md-color-scheme=default]):not([data-md-color-scheme=dracula]) .md-icon .dark-mode,body:not([data-md-prefers-color-scheme=true]):not([data-md-color-scheme=default]):not([data-md-color-scheme=dracula]) .md-icon .light-mode,body:not([data-md-prefers-color-scheme=true]):not([data-md-color-scheme=default]):not([data-md-color-scheme=dracula]) .md-icon .system-mode{display:none}body[data-md-prefers-color-scheme=true] .md-icon .dark-mode,body[data-md-prefers-color-scheme=true] .md-icon .light-mode,body[data-md-prefers-color-scheme=true] .md-icon .unknown-mode{display:none}.md-header-nav__scheme{z-index:0}[data-md-toggle=search]:checked~.md-header .md-header-nav__scheme{display:none}.md-typeset .admonition,.md-typeset details{border-width:0;border-left-width:4px}:root>*{--md-admonition-bg-color:transparent;--md-admonition-icon--settings:url('data:image/svg+xml;charset=utf-8,');--md-admonition-bg-color--settings:rgba(170, 0, 255, 0.1);--md-admonition-icon-color--settings:#aa00ff;--md-admonition-shadow-color--settings:rgba(170, 0, 255, 0.1);--md-admonition-icon--new:url('data:image/svg+xml;charset=utf-8,');--md-admonition-bg-color--new:rgba(255, 214, 0, 0.1);--md-admonition-icon-color--new:#ffd600;--md-admonition-shadow-color--new:rgba(255, 214, 0, 0.1);--md-admonition-bg-color--note:var(--md-default-bg-color--ultra-dark);--md-admonition-icon-color--note:hsl(51, 94%, 73%);--md-admonition-shadow-color--note:rgba(251, 231, 121, 0.1);--md-admonition-bg-color--abstract:var(--md-default-bg-color--ultra-dark);--md-admonition-icon-color--abstract:hsl(191, 97%, 77%);--md-admonition-shadow-color--abstract:rgba(139, 232, 253, 0.1);--md-admonition-bg-color--info:var(--md-default-bg-color--ultra-dark);--md-admonition-icon-color--info:hsl(190, 94%, 87%);--md-admonition-shadow-color--info:rgba(191, 243, 253, 0.1);--md-admonition-bg-color--tip:var(--md-default-bg-color--ultra-dark);--md-admonition-icon-color--tip:hsl(161, 97%, 77%);--md-admonition-shadow-color--tip:rgba(139, 253, 217, 0.1);--md-admonition-bg-color--success:var(--md-default-bg-color--ultra-dark);--md-admonition-icon-color--success:hsl(135, 94%, 65%);--md-admonition-shadow-color--success:rgba(82, 250, 124, 0.1);--md-admonition-bg-color--question:var(--md-default-bg-color--ultra-dark);--md-admonition-icon-color--question:hsl(135, 92%, 79%);--md-admonition-shadow-color--question:rgba(152, 251, 177, 0.1);--md-admonition-bg-color--warning:var(--md-default-bg-color--ultra-dark);--md-admonition-icon-color--warning:hsl(31, 100%, 71%);--md-admonition-shadow-color--warning:rgba(255, 184, 107, 0.1);--md-admonition-bg-color--failure:var(--md-default-bg-color--ultra-dark);--md-admonition-icon-color--failure:hsl(0, 100%, 59%);--md-admonition-shadow-color--failure:rgba(255, 46, 46, 0.1);--md-admonition-bg-color--danger:var(--md-default-bg-color--ultra-dark);--md-admonition-icon-color--danger:hsl(0, 100%, 67%);--md-admonition-shadow-color--danger:rgba(255, 87, 87, 0.1);--md-admonition-bg-color--bug:var(--md-default-bg-color--ultra-dark);--md-admonition-icon-color--bug:hsl(325, 100%, 64%);--md-admonition-shadow-color--bug:rgba(255, 71, 179, 0.1);--md-admonition-bg-color--example:var(--md-default-bg-color--ultra-dark);--md-admonition-icon-color--example:hsl(265, 89%, 78%);--md-admonition-shadow-color--example:rgba(191, 149, 249, 0.1);--md-admonition-bg-color--quote:var(--md-default-bg-color--ultra-dark);--md-admonition-icon-color--quote:hsl(225, 8%, 51%);--md-admonition-shadow-color--quote:rgba(120, 125, 140, 0.1)}:root>[data-md-color-scheme=dracula]{--md-admonition-icon-color:$drac-dark-yellow}:root>[data-md-color-scheme=dracula]{--md-admonition-bg-color--settings:var(--md-default-bg-color--ultra-dark);--md-admonition-icon-color--settings:hsl(326, 100%, 74%);--md-admonition-shadow-color--settings:rgba(255, 122, 198, 0.1)}:root>[data-md-color-scheme=dracula]{--md-admonition-bg-color--new:var(--md-default-bg-color--ultra-dark);--md-admonition-icon-color--new:hsl(65, 92%, 76%);--md-admonition-shadow-color--new:rgba(241, 250, 137, 0.1)}[data-md-color-scheme=dracula] .md-typeset .admonition,[data-md-color-scheme=dracula] .md-typeset details{border-color:var(--md-admonition-icon-color--note);box-shadow:var(--md-shadow-z2)}[data-md-color-scheme=dracula] .md-typeset .admonition:focus-within,[data-md-color-scheme=dracula] .md-typeset details:focus-within{box-shadow:0 0 0 .2rem var(--md-admonition-shadow-color--note)}[data-md-color-scheme=dracula] .md-typeset .admonition>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details>summary{background-color:var(--md-admonition-bg-color--note)}[data-md-color-scheme=dracula] .md-typeset .admonition>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details>summary::before{background-color:var(--md-admonition-icon-color--note)}[data-md-color-scheme=dracula] .md-typeset .admonition>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details>summary::after{color:var(--md-admonition-icon-color--note)}[data-md-color-scheme=dracula] .md-typeset .admonition.note,[data-md-color-scheme=dracula] .md-typeset details.note{border-color:var(--md-admonition-icon-color--note)}[data-md-color-scheme=dracula] .md-typeset .admonition.note:focus-within,[data-md-color-scheme=dracula] .md-typeset details.note:focus-within{box-shadow:0 0 0 .2rem var(--md-admonition-shadow-color--note)}[data-md-color-scheme=dracula] .md-typeset .admonition.note>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.note>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.note>summary{background-color:var(--md-admonition-bg-color--note);border-color:var(--md-admonition-icon-color--note)}[data-md-color-scheme=dracula] .md-typeset .admonition.note>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.note>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.note>summary::before{background-color:var(--md-admonition-icon-color--note)}[data-md-color-scheme=dracula] .md-typeset .admonition.note>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.note>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.note>summary::after{color:var(--md-admonition-icon-color--note)}[data-md-color-scheme=dracula] .md-typeset .admonition.abstract,[data-md-color-scheme=dracula] .md-typeset details.abstract{border-color:var(--md-admonition-icon-color--abstract)}[data-md-color-scheme=dracula] .md-typeset .admonition.abstract:focus-within,[data-md-color-scheme=dracula] .md-typeset details.abstract:focus-within{box-shadow:0 0 0 .2rem var(--md-admonition-shadow-color--abstract)}[data-md-color-scheme=dracula] .md-typeset .admonition.abstract>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.abstract>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.abstract>summary{background-color:var(--md-admonition-bg-color--abstract);border-color:var(--md-admonition-icon-color--abstract)}[data-md-color-scheme=dracula] .md-typeset .admonition.abstract>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.abstract>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.abstract>summary::before{background-color:var(--md-admonition-icon-color--abstract)}[data-md-color-scheme=dracula] .md-typeset .admonition.abstract>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.abstract>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.abstract>summary::after{color:var(--md-admonition-icon-color--abstract)}[data-md-color-scheme=dracula] .md-typeset .admonition.info,[data-md-color-scheme=dracula] .md-typeset details.info{border-color:var(--md-admonition-icon-color--info)}[data-md-color-scheme=dracula] .md-typeset .admonition.info:focus-within,[data-md-color-scheme=dracula] .md-typeset details.info:focus-within{box-shadow:0 0 0 .2rem var(--md-admonition-shadow-color--info)}[data-md-color-scheme=dracula] .md-typeset .admonition.info>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.info>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.info>summary{background-color:var(--md-admonition-bg-color--info);border-color:var(--md-admonition-icon-color--info)}[data-md-color-scheme=dracula] .md-typeset .admonition.info>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.info>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.info>summary::before{background-color:var(--md-admonition-icon-color--info)}[data-md-color-scheme=dracula] .md-typeset .admonition.info>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.info>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.info>summary::after{color:var(--md-admonition-icon-color--info)}[data-md-color-scheme=dracula] .md-typeset .admonition.tip,[data-md-color-scheme=dracula] .md-typeset details.tip{border-color:var(--md-admonition-icon-color--tip)}[data-md-color-scheme=dracula] .md-typeset .admonition.tip:focus-within,[data-md-color-scheme=dracula] .md-typeset details.tip:focus-within{box-shadow:0 0 0 .2rem var(--md-admonition-shadow-color--tip)}[data-md-color-scheme=dracula] .md-typeset .admonition.tip>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.tip>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.tip>summary{background-color:var(--md-admonition-bg-color--tip);border-color:var(--md-admonition-icon-color--tip)}[data-md-color-scheme=dracula] .md-typeset .admonition.tip>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.tip>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.tip>summary::before{background-color:var(--md-admonition-icon-color--tip)}[data-md-color-scheme=dracula] .md-typeset .admonition.tip>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.tip>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.tip>summary::after{color:var(--md-admonition-icon-color--tip)}[data-md-color-scheme=dracula] .md-typeset .admonition.success,[data-md-color-scheme=dracula] .md-typeset details.success{border-color:var(--md-admonition-icon-color--success)}[data-md-color-scheme=dracula] .md-typeset .admonition.success:focus-within,[data-md-color-scheme=dracula] .md-typeset details.success:focus-within{box-shadow:0 0 0 .2rem var(--md-admonition-shadow-color--success)}[data-md-color-scheme=dracula] .md-typeset .admonition.success>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.success>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.success>summary{background-color:var(--md-admonition-bg-color--success);border-color:var(--md-admonition-icon-color--success)}[data-md-color-scheme=dracula] .md-typeset .admonition.success>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.success>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.success>summary::before{background-color:var(--md-admonition-icon-color--success)}[data-md-color-scheme=dracula] .md-typeset .admonition.success>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.success>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.success>summary::after{color:var(--md-admonition-icon-color--success)}[data-md-color-scheme=dracula] .md-typeset .admonition.question,[data-md-color-scheme=dracula] .md-typeset details.question{border-color:var(--md-admonition-icon-color--question)}[data-md-color-scheme=dracula] .md-typeset .admonition.question:focus-within,[data-md-color-scheme=dracula] .md-typeset details.question:focus-within{box-shadow:0 0 0 .2rem var(--md-admonition-shadow-color--question)}[data-md-color-scheme=dracula] .md-typeset .admonition.question>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.question>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.question>summary{background-color:var(--md-admonition-bg-color--question);border-color:var(--md-admonition-icon-color--question)}[data-md-color-scheme=dracula] .md-typeset .admonition.question>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.question>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.question>summary::before{background-color:var(--md-admonition-icon-color--question)}[data-md-color-scheme=dracula] .md-typeset .admonition.question>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.question>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.question>summary::after{color:var(--md-admonition-icon-color--question)}[data-md-color-scheme=dracula] .md-typeset .admonition.warning,[data-md-color-scheme=dracula] .md-typeset details.warning{border-color:var(--md-admonition-icon-color--warning)}[data-md-color-scheme=dracula] .md-typeset .admonition.warning:focus-within,[data-md-color-scheme=dracula] .md-typeset details.warning:focus-within{box-shadow:0 0 0 .2rem var(--md-admonition-shadow-color--warning)}[data-md-color-scheme=dracula] .md-typeset .admonition.warning>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.warning>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.warning>summary{background-color:var(--md-admonition-bg-color--warning);border-color:var(--md-admonition-icon-color--warning)}[data-md-color-scheme=dracula] .md-typeset .admonition.warning>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.warning>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.warning>summary::before{background-color:var(--md-admonition-icon-color--warning)}[data-md-color-scheme=dracula] .md-typeset .admonition.warning>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.warning>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.warning>summary::after{color:var(--md-admonition-icon-color--warning)}[data-md-color-scheme=dracula] .md-typeset .admonition.failure,[data-md-color-scheme=dracula] .md-typeset details.failure{border-color:var(--md-admonition-icon-color--failure)}[data-md-color-scheme=dracula] .md-typeset .admonition.failure:focus-within,[data-md-color-scheme=dracula] .md-typeset details.failure:focus-within{box-shadow:0 0 0 .2rem var(--md-admonition-shadow-color--failure)}[data-md-color-scheme=dracula] .md-typeset .admonition.failure>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.failure>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.failure>summary{background-color:var(--md-admonition-bg-color--failure);border-color:var(--md-admonition-icon-color--failure)}[data-md-color-scheme=dracula] .md-typeset .admonition.failure>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.failure>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.failure>summary::before{background-color:var(--md-admonition-icon-color--failure)}[data-md-color-scheme=dracula] .md-typeset .admonition.failure>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.failure>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.failure>summary::after{color:var(--md-admonition-icon-color--failure)}[data-md-color-scheme=dracula] .md-typeset .admonition.danger,[data-md-color-scheme=dracula] .md-typeset details.danger{border-color:var(--md-admonition-icon-color--danger)}[data-md-color-scheme=dracula] .md-typeset .admonition.danger:focus-within,[data-md-color-scheme=dracula] .md-typeset details.danger:focus-within{box-shadow:0 0 0 .2rem var(--md-admonition-shadow-color--danger)}[data-md-color-scheme=dracula] .md-typeset .admonition.danger>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.danger>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.danger>summary{background-color:var(--md-admonition-bg-color--danger);border-color:var(--md-admonition-icon-color--danger)}[data-md-color-scheme=dracula] .md-typeset .admonition.danger>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.danger>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.danger>summary::before{background-color:var(--md-admonition-icon-color--danger)}[data-md-color-scheme=dracula] .md-typeset .admonition.danger>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.danger>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.danger>summary::after{color:var(--md-admonition-icon-color--danger)}[data-md-color-scheme=dracula] .md-typeset .admonition.bug,[data-md-color-scheme=dracula] .md-typeset details.bug{border-color:var(--md-admonition-icon-color--bug)}[data-md-color-scheme=dracula] .md-typeset .admonition.bug:focus-within,[data-md-color-scheme=dracula] .md-typeset details.bug:focus-within{box-shadow:0 0 0 .2rem var(--md-admonition-shadow-color--bug)}[data-md-color-scheme=dracula] .md-typeset .admonition.bug>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.bug>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.bug>summary{background-color:var(--md-admonition-bg-color--bug);border-color:var(--md-admonition-icon-color--bug)}[data-md-color-scheme=dracula] .md-typeset .admonition.bug>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.bug>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.bug>summary::before{background-color:var(--md-admonition-icon-color--bug)}[data-md-color-scheme=dracula] .md-typeset .admonition.bug>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.bug>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.bug>summary::after{color:var(--md-admonition-icon-color--bug)}[data-md-color-scheme=dracula] .md-typeset .admonition.example,[data-md-color-scheme=dracula] .md-typeset details.example{border-color:var(--md-admonition-icon-color--example)}[data-md-color-scheme=dracula] .md-typeset .admonition.example:focus-within,[data-md-color-scheme=dracula] .md-typeset details.example:focus-within{box-shadow:0 0 0 .2rem var(--md-admonition-shadow-color--example)}[data-md-color-scheme=dracula] .md-typeset .admonition.example>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.example>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.example>summary{background-color:var(--md-admonition-bg-color--example);border-color:var(--md-admonition-icon-color--example)}[data-md-color-scheme=dracula] .md-typeset .admonition.example>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.example>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.example>summary::before{background-color:var(--md-admonition-icon-color--example)}[data-md-color-scheme=dracula] .md-typeset .admonition.example>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.example>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.example>summary::after{color:var(--md-admonition-icon-color--example)}[data-md-color-scheme=dracula] .md-typeset .admonition.quote,[data-md-color-scheme=dracula] .md-typeset details.quote{border-color:var(--md-admonition-icon-color--quote)}[data-md-color-scheme=dracula] .md-typeset .admonition.quote:focus-within,[data-md-color-scheme=dracula] .md-typeset details.quote:focus-within{box-shadow:0 0 0 .2rem var(--md-admonition-shadow-color--quote)}[data-md-color-scheme=dracula] .md-typeset .admonition.quote>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.quote>.admonition-title,[data-md-color-scheme=dracula] .md-typeset details.quote>summary{background-color:var(--md-admonition-bg-color--quote);border-color:var(--md-admonition-icon-color--quote)}[data-md-color-scheme=dracula] .md-typeset .admonition.quote>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.quote>.admonition-title::before,[data-md-color-scheme=dracula] .md-typeset details.quote>summary::before{background-color:var(--md-admonition-icon-color--quote)}[data-md-color-scheme=dracula] .md-typeset .admonition.quote>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.quote>.admonition-title::after,[data-md-color-scheme=dracula] .md-typeset details.quote>summary::after{color:var(--md-admonition-icon-color--quote)}.md-typeset .admonition.config,.md-typeset .admonition.settings,.md-typeset details.config,.md-typeset details.settings{border-color:var(--md-admonition-icon-color--settings)}.md-typeset .admonition.config:focus-within,.md-typeset .admonition.settings:focus-within,.md-typeset details.config:focus-within,.md-typeset details.settings:focus-within{box-shadow:0 0 0 .2rem var(--md-admonition-shadow-color--settings)}.md-typeset .admonition.config>.admonition-title,.md-typeset .admonition.settings>.admonition-title,.md-typeset details.config>.admonition-title,.md-typeset details.config>summary,.md-typeset details.settings>.admonition-title,.md-typeset details.settings>summary{background-color:var(--md-admonition-bg-color--settings);border-color:var(--md-admonition-icon-color--settings)}.md-typeset .admonition.config>.admonition-title::before,.md-typeset .admonition.settings>.admonition-title::before,.md-typeset details.config>.admonition-title::before,.md-typeset details.config>summary::before,.md-typeset details.settings>.admonition-title::before,.md-typeset details.settings>summary::before{width:1rem;height:1rem;background-color:var(--md-admonition-icon-color--settings);background-size:1rem;-webkit-mask-image:var(--md-admonition-icon--settings);mask-image:var(--md-admonition-icon--settings);content:" "}.md-typeset .admonition.config>.admonition-title::after,.md-typeset .admonition.settings>.admonition-title::after,.md-typeset details.config>.admonition-title::after,.md-typeset details.config>summary::after,.md-typeset details.settings>.admonition-title::after,.md-typeset details.settings>summary::after{color:var(--md-admonition-icon-color--settings)}.md-typeset .admonition.new,.md-typeset details.new{border-color:var(--md-admonition-icon-color--new)}.md-typeset .admonition.new:focus-within,.md-typeset details.new:focus-within{box-shadow:0 0 0 .2rem var(--md-admonition-shadow-color--new)}.md-typeset .admonition.new>.admonition-title,.md-typeset details.new>.admonition-title,.md-typeset details.new>summary{background-color:var(--md-admonition-bg-color--new);border-color:var(--md-admonition-icon-color--new)}.md-typeset .admonition.new>.admonition-title::before,.md-typeset details.new>.admonition-title::before,.md-typeset details.new>summary::before{width:1rem;height:1rem;background-color:var(--md-admonition-icon-color--new);background-size:1rem;-webkit-mask-image:var(--md-admonition-icon--new);mask-image:var(--md-admonition-icon--new);content:" "}.md-typeset .admonition.new>.admonition-title::after,.md-typeset details.new>.admonition-title::after,.md-typeset details.new>summary::after{color:var(--md-admonition-icon-color--new)}mjx-container[display=true]{font-size:120%!important}mjx-container:not([display]){font-size:100%!important}[data-md-color-scheme=dracula] .CtxtMenu_InfoContent pre,[data-md-color-scheme=dracula] .CtxtMenu_InfoSignature input,[data-md-color-scheme=slate] .CtxtMenu_InfoContent pre,[data-md-color-scheme=slate] .CtxtMenu_InfoSignature input{color:#000}[data-md-color-scheme=dracula] .CtxtMenu_Info,[data-md-color-scheme=dracula] .CtxtMenu_Menu,[data-md-color-scheme=slate] .CtxtMenu_Info,[data-md-color-scheme=slate] .CtxtMenu_Menu{box-shadow:0 10px 20px rgba(0,0,0,.5)}.md-typeset .arithmatex{overflow-x:auto!important;overflow-y:hidden!important}.katex-display .katex-html{display:flex!important;flex-direction:row;flex-wrap:nowrap;align-items:baseline;justify-content:space-between}.katex-display .katex-html .base{display:inline!important}.katex-display .katex-html .tag{position:relative!important;display:inline!important;margin-left:var(--margin-small)}.md-typeset del.critic,.md-typeset ins.critic,.md-typeset mark.critic{padding:0 .25em;color:unset;box-shadow:none}.md-typeset .critic.break{margin:0}.md-typeset details{overflow:hidden}.md-typeset details>summary:focus{outline-style:none}.highlight .kc{color:var(--md-code-hl-constant-color)}.highlight .nc,.highlight .ne{color:var(--md-code-hl-class-color)}.highlight .mb{color:var(--md-code-hl-number-color)}.highlight .bp,.highlight .nb{color:var(--md-code-hl-builtin-color)}.highlight .nn{color:var(--md-code-hl-namespace-color)}.highlight .na,.highlight .nd,.highlight .ni{color:var(--md-code-hl-entity-color)}.highlight .nl,.highlight .nt{color:var(--md-code-hl-tag-color)}.md-typeset :not(pre)>code{margin:0;padding:0 .2941176471em;color:var(--md-code-fg-color);background-color:var(--md-code-inline-bg-color);border-radius:.1rem;box-shadow:none}.md-typeset a>code{color:inherit!important;background-color:var(--md-code-link-bg-color)!important;transition:color 125ms;transition:background-color 125ms}.md-typeset a>code *{color:var(--md-typeset-a-color)!important}.md-typeset a>code:hover{background-color:var(--md-code-link-accent-bg-color)!important}.md-typeset a>code:hover *{color:var(--md-accent-fg-color)!important}.md-typeset pre>code{outline:0}.md-typeset td code{word-break:normal}.md-typeset .highlight{-moz-tab-size:8;-o-tab-size:8;tab-size:8}.md-typeset .highlight+.result{border-width:.1rem}.md-typeset .highlight [data-linenos].special::before{background-color:var(--md-code-special-bg-color)}.md-typeset .highlighttable .linenodiv .special{margin-right:-.5882352941em;margin-left:-1.1764705882em;padding-right:.5882352941em;padding-left:1.1764705882em;background-color:var(--md-code-special-bg-color)}.md-typeset .highlight span.filename{position:relative;display:block;margin-top:1em;padding:.5em 1.1764705882em .5em 2.9411764706em;font-weight:700;font-size:.68rem;background-color:var(--md-code-title-bg-color);border-top-left-radius:.1rem;border-top-right-radius:.1rem}.md-typeset .highlight span.filename+pre{margin-top:0}.md-typeset .highlight span.filename+pre code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .highlight span.filename::before{position:absolute;left:.8823529412em;width:1.4705882353em;height:1.4705882353em;background-color:var(--md-default-fg-color);-webkit-mask-image:url('data:image/svg+xml;charset=utf-8,');mask-image:url('data:image/svg+xml;charset=utf-8,');-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-typeset .collapse-code{position:relative;margin-top:1em;margin-bottom:1em}.md-typeset .collapse-code pre{margin-top:0;margin-bottom:0}.md-typeset .collapse-code input{display:none}.md-typeset .collapse-code input~.code-footer{width:100%;margin:0;padding:.25em .5em .25em 0}.md-typeset .collapse-code input~.code-footer label{position:relative;margin:.05em;padding:.15em .8em;color:var(--md-primary-bg-color);font-size:90%;background-color:var(--md-primary-fg-color);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;border-radius:.1rem;cursor:pointer;content:""}.md-typeset .collapse-code input~.code-footer label:hover{background-color:var(--md-accent-fg-color)}.md-typeset .collapse-code input~.code-footer label::before{position:absolute;top:.15em;left:.15em;display:block;box-sizing:border-box;width:1.25em;height:1.25em;background-color:var(--md-primary-bg-color);background-size:1.25em;content:""}.md-typeset .collapse-code input~.code-footer label.expand{display:none}.md-typeset .collapse-code input~.code-footer label.expand::before{-webkit-mask-image:url('data:image/svg+xml;charset=utf-8,');mask-image:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .collapse-code input~.code-footer label.collapse::before{-webkit-mask-image:url('data:image/svg+xml;charset=utf-8,');mask-image:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .collapse-code input:checked~.code-footer label.expand{display:inline}.md-typeset .collapse-code input:checked~.code-footer label.collapse{display:none}.md-typeset .collapse-code input:checked+div.highlight code{max-height:9.375em;overflow:hidden}.md-typeset .collapse-code input:checked~.code-footer{position:absolute;bottom:0;left:0;padding:2em .5em .5em .8rem;background-image:linear-gradient(to bottom,transparent,var(--md-default-bg-color) 80% 100%)}.md-typeset .keys .key-power::before{padding-right:.4em;content:"⏻"}.md-typeset .keys .key-fingerprint::before{padding-right:.4em;content:"☝"}:root>*{--magiclink-email-icon:url('data:image/svg+xml;charset=utf-8,');--magiclink-github-icon:url('data:image/svg+xml;charset=utf-8,');--magiclink-bitbucket-icon:url('data:image/svg+xml;charset=utf-8,');--magiclink-gitlab-icon:url('data:image/svg+xml;charset=utf-8,');--magiclink-commit-icon:url('data:image/svg+xml;charset=utf-8,');--magiclink-compare-icon:url('data:image/svg+xml;charset=utf-8,');--magiclink-pull-icon:url('data:image/svg+xml;charset=utf-8,');--magiclink-issue-icon:url('data:image/svg+xml;charset=utf-8,');--magiclink-discussion-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset a[href^="mailto:"]:not(.magiclink-ignore)::before{-webkit-mask-image:var(--magiclink-email-icon);mask-image:var(--magiclink-email-icon)}.md-typeset .magiclink-commit:not(.magiclink-ignore),.md-typeset .magiclink-compare:not(.magiclink-ignore),.md-typeset .magiclink-discussion:not(.magiclink-ignore),.md-typeset .magiclink-issue:not(.magiclink-ignore),.md-typeset .magiclink-pull:not(.magiclink-ignore),.md-typeset .magiclink-repository:not(.magiclink-ignore),.md-typeset a[href^="mailto:"]:not(.magiclink-ignore){position:relative;padding-left:1.375em}.md-typeset .magiclink-commit:not(.magiclink-ignore)::before,.md-typeset .magiclink-compare:not(.magiclink-ignore)::before,.md-typeset .magiclink-discussion:not(.magiclink-ignore)::before,.md-typeset .magiclink-issue:not(.magiclink-ignore)::before,.md-typeset .magiclink-pull:not(.magiclink-ignore)::before,.md-typeset .magiclink-repository:not(.magiclink-ignore)::before,.md-typeset a[href^="mailto:"]:not(.magiclink-ignore)::before{position:absolute;top:0;left:0;display:block;box-sizing:border-box;width:1.25em;height:1.25em;background-color:var(--md-typeset-a-color);background-size:1.25em;transition:background-color 125ms;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;content:""}.md-typeset .magiclink-commit:not(.magiclink-ignore):hover::before,.md-typeset .magiclink-compare:not(.magiclink-ignore):hover::before,.md-typeset .magiclink-discussion:not(.magiclink-ignore):hover::before,.md-typeset .magiclink-issue:not(.magiclink-ignore):hover::before,.md-typeset .magiclink-pull:not(.magiclink-ignore):hover::before,.md-typeset .magiclink-repository:not(.magiclink-ignore):hover::before,.md-typeset a[href^="mailto:"]:not(.magiclink-ignore):hover::before{background-color:var(--md-accent-fg-color)}.md-typeset .magiclink-commit:not(.magiclink-ignore)::before{-webkit-mask-image:var(--magiclink-commit-icon);mask-image:var(--magiclink-commit-icon)}.md-typeset .magiclink-compare:not(.magiclink-ignore)::before{-webkit-mask-image:var(--magiclink-compare-icon);mask-image:var(--magiclink-compare-icon)}.md-typeset .magiclink-pull:not(.magiclink-ignore)::before{-webkit-mask-image:var(--magiclink-pull-icon);mask-image:var(--magiclink-pull-icon)}.md-typeset .magiclink-issue:not(.magiclink-ignore)::before{-webkit-mask-image:var(--magiclink-issue-icon);mask-image:var(--magiclink-issue-icon)}.md-typeset .magiclink-discussion:not(.magiclink-ignore)::before{-webkit-mask-image:var(--magiclink-discussion-icon);mask-image:var(--magiclink-discussion-icon)}.md-typeset .magiclink-repository.magiclink-github:not(.magiclink-ignore)::before{-webkit-mask-image:var(--magiclink-github-icon);mask-image:var(--magiclink-github-icon)}.md-typeset .magiclink-repository.magiclink-gitlab:not(.magiclink-ignore)::before{-webkit-mask-image:var(--magiclink-gitlab-icon);mask-image:var(--magiclink-gitlab-icon)}.md-typeset .magiclink-repository.magiclink-bitbucket:not(.magiclink-ignore)::before{-webkit-mask-image:var(--magiclink-bitbucket-icon);mask-image:var(--magiclink-bitbucket-icon)}.md-typeset mark:not(.critic){box-shadow:none}.md-typeset .progress-label{position:absolute;width:100%;margin:0;color:var(--md-text-color);font-weight:700;line-height:1.4rem;white-space:nowrap;text-align:center;text-shadow:-.0625em -.0625em .375em var(--md-default-bg-color--light),.0625em -.0625em .375em var(--md-default-bg-color--light),-.0625em .0625em .375em var(--md-default-bg-color--light),.0625em .0625em .375em var(--md-default-bg-color--light)}.md-typeset .progress-bar{float:left;height:1.2rem;background-color:#2979ff}.md-typeset .candystripe-animate .progress-bar{animation:animate-stripes 3s linear infinite}.md-typeset .progress{position:relative;display:block;width:100%;height:1.2rem;margin:.5rem 0;background-color:var(--md-default-fg-color--lightest)}.md-typeset .progress.thin{height:.4rem;margin-top:.9rem}.md-typeset .progress.thin .progress-label{margin-top:-.4rem}.md-typeset .progress.thin .progress-bar{height:.4rem}.md-typeset .progress.candystripe .progress-bar{background-image:linear-gradient(135deg,var(--md-progress-stripe) 27%,transparent 27%,transparent 52%,var(--md-progress-stripe) 52%,var(--md-progress-stripe) 77%,transparent 77%,transparent);background-size:2rem 2rem}.md-typeset .progress-100plus .progress-bar{background-color:var(--md-progress-100)}.md-typeset .progress-80plus .progress-bar{background-color:var(--md-progress-80)}.md-typeset .progress-60plus .progress-bar{background-color:var(--md-progress-60)}.md-typeset .progress-40plus .progress-bar{background-color:var(--md-progress-40)}.md-typeset .progress-20plus .progress-bar{background-color:var(--md-progress-20)}.md-typeset .progress-0plus .progress-bar{background-color:var(--md-progress-0)}@keyframes animate-stripes{0%{background-position:0 0}100%{background-position:6rem 0}}[data-md-color-scheme=dracula] .md-typeset .tabbed-set>.tabbed-labels{box-shadow:0 -.05rem var(--md-default-fg-color--lighter) inset}.md-typeset .tabbed-alternate.tabbed-set .tabbed-control{width:2rem}.md-typeset .tabbed-alternate.tabbed-set .tabbed-control[hidden]{width:1.2rem;opacity:0}.md-typeset .tabbed-alternate.tabbed-set>.tabbed-content>.tabbed-block{padding:0 .6rem}.md-typeset .tabbed-alternate.tabbed-set>.tabbed-content>.tabbed-block>.codehilite:only-child,.md-typeset .tabbed-alternate.tabbed-set>.tabbed-content>.tabbed-block>.codehilitetable:only-child,.md-typeset .tabbed-alternate.tabbed-set>.tabbed-content>.tabbed-block>.highlight:only-child,.md-typeset .tabbed-alternate.tabbed-set>.tabbed-content>.tabbed-block>.highlighttable:only-child,.md-typeset .tabbed-alternate.tabbed-set>.tabbed-content>.tabbed-block>pre:only-child{margin-right:-1.2rem;margin-left:-1.2rem;padding-right:.6rem;padding-left:.6rem}.md-typeset .tabbed-alternate.tabbed-set>.tabbed-content>.tabbed-block>.codehilite:only-child span.filename,.md-typeset .tabbed-alternate.tabbed-set>.tabbed-content>.tabbed-block>.codehilitetable:only-child span.filename,.md-typeset .tabbed-alternate.tabbed-set>.tabbed-content>.tabbed-block>.highlight:only-child span.filename,.md-typeset .tabbed-alternate.tabbed-set>.tabbed-content>.tabbed-block>.highlighttable:only-child span.filename,.md-typeset .tabbed-alternate.tabbed-set>.tabbed-content>.tabbed-block>pre:only-child span.filename{margin-top:0}.md-typeset .tabbed-alternate.tabbed-set>.tabbed-content>.tabbed-block>.collapse-code:only-child{margin-top:0;margin-right:-1.2rem;margin-left:-1.2rem;padding-right:.6rem;padding-left:.6rem}.md-typeset .tabbed-alternate.tabbed-set>.tabbed-content>.tabbed-block>.collapse-code:only-child>.code-footer{left:.6rem}.md-typeset .tabbed-alternate.tabbed-set>.tabbed-content>.tabbed-block>diagram-div:only-child{margin-right:-1.2rem;margin-left:-1.2rem;padding-right:.6rem;padding-left:.6rem}.js .md-typeset .tabbed-labels::before{background-color:var(--md-accent-fg-color)}[data-md-color-scheme=dracula] .md-typeset table:not([class]){box-shadow:var(--md-shadow-z2)}[data-md-color-scheme=dracula] .md-typeset table:not([class]) tr:hover{background-color:rgba(0,0,0,.08)}[data-md-color-scheme=dracula] .md-typeset table:not([class]) th{color:var(--md-text-color);background-color:var(--md-default-bg-color--ultra-dark);border-bottom:.05rem solid var(--md-primary-fg-color)}[data-md-color-scheme=dracula] .md-typeset table:not([class]) td{border-top:.05rem solid var(--md-default-fg-color--lighter)}[data-md-color-scheme=dracula] .md-typeset .task-list-control .task-list-indicator::before{background-color:var(--md-default-fg-color--lighter)}[data-md-color-scheme=dracula] .md-typeset .task-list-control [type=checkbox]:checked+.task-list-indicator::before{background-color:#51f97b}.md-typeset .headerlink{width:1em;height:1em;vertical-align:middle;background-color:var(--md-default-fg-color--lighter);background-size:1em;-webkit-mask-size:1em;mask-size:1em;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;visibility:visible;-webkit-mask-image:url('data:image/svg+xml;charset=utf-8,');mask-image:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .headerlink:hover,.md-typeset [id]:target .headerlink{background-color:var(--md-accent-fg-color)}diagram-div{overflow:auto}html{background-color:transparent}[data-md-component=announce] .twemoji{color:var(--md-primary-fg-color)}[data-md-color-scheme=dracula]{--md-text-color:var(--md-default-fg-color);background-color:var(--md-default-bg-color);--md-footer-bg-color:transparent;--md-footer-bg-color--dark:var(--md-default-bg-color--darkest);--md-header-fg-color:var(--md-text-color);--md-header-bg-color:var(--md-default-bg-color--darkest)}[data-md-color-scheme=dracula] .md-header{color:var(--md-text-color);background-color:var(--md-header-bg-color);border-bottom:.05rem solid var(--md-primary-fg-color)}[data-md-color-scheme=dracula] .md-header[data-md-state=shadow]{box-shadow:0 0 .2rem rgba(0,0,0,.15),0 0 .2rem .4rem rgba(0,0,0,.2)}[data-md-color-scheme=dracula] .md-top{background-color:var(--md-default-bg-color--dark)}[data-md-color-scheme=dracula] .md-top:hover{background-color:var(--md-primary-fg-color)}[data-md-color-scheme=dracula] .md-tabs{color:var(--md-text-color);background-color:var(--md-primary-fg-color--transparent)}[data-md-color-scheme=dracula] .md-tabs__link--active{color:var(--md-primary-fg-color)}[data-md-color-scheme=dracula] .md-tabs__link:hover{color:var(--md-accent-fg-color)}[data-md-color-scheme=dracula] .md-hero{color:var(--md-text-color);background-color:var(--md-primary-fg-color--transparent)}[data-md-color-scheme=dracula] .md-nav__source{color:var(--md-text-color)}[data-md-color-scheme=dracula] .md-nav__link[data-md-state=blur]{color:var(--md-default-fg-color--light)}[data-md-color-scheme=dracula] .md-nav__item .md-nav__link--active{color:var(--md-primary-fg-color)}[data-md-color-scheme=dracula] .md-nav__link:focus,[data-md-color-scheme=dracula] .md-nav__link:hover{color:var(--md-accent-fg-color)}[data-md-color-scheme=dracula] .md-search__input{color:var(--md-text-color);background-color:var(--md-accent-bg-color--light)}[data-md-color-scheme=dracula] .md-search__input:hover{background-color:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] .md-search__input~.md-search__icon{color:var(--md-text-color)}[data-md-color-scheme=dracula] .md-search__input::-moz-placeholder{color:var(--md-default-fg-color--light)}[data-md-color-scheme=dracula] .md-search__input::placeholder{color:var(--md-default-fg-color--light)}[data-md-color-scheme=dracula] [data-md-toggle=search]:checked~.md-header .md-search__input{background-color:transparent}[data-md-color-scheme=dracula] .md-search__suggest{color:var(--md-default-autocomplete-fg-color)}[data-md-color-scheme=dracula] .md-overlay,[data-md-color-scheme=dracula] .md-search__overlay{background-color:var(--md-default-bg-color--light)}[data-md-color-scheme=dracula] .md-footer-nav__direction{color:var(--md-primary-fg-color)}[data-md-color-scheme=dracula] .md-footer-meta{border-top:.05rem solid var(--md-primary-fg-color)}[data-md-color-scheme=dracula] [data-md-component=announce]{background-color:var(--md-default-bg-color--ultra-dark)}.md-typeset h5{color:var(--md-text-color);text-transform:none}.md-search__scrollwrap,.md-sidebar__scrollwrap,.md-typeset diagram-div,.md-typeset div.arithmatex,.md-typeset div.diagram,.md-typeset div.mermaid,.md-typeset mermaid-div,.md-typeset pre.arithmatex,.md-typeset pre>code,.md-typeset__scrollwrap{scrollbar-color:var(--md-default-fg-color--lighter) transparent;scrollbar-width:thin}.md-search__scrollwrap::-webkit-scrollbar,.md-sidebar__scrollwrap::-webkit-scrollbar,.md-typeset diagram-div::-webkit-scrollbar,.md-typeset div.arithmatex::-webkit-scrollbar,.md-typeset div.diagram::-webkit-scrollbar,.md-typeset div.mermaid::-webkit-scrollbar,.md-typeset mermaid-div::-webkit-scrollbar,.md-typeset pre.arithmatex::-webkit-scrollbar,.md-typeset pre>code::-webkit-scrollbar,.md-typeset__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-search__scrollwrap::-webkit-scrollbar-corner,.md-sidebar__scrollwrap::-webkit-scrollbar-corner,.md-typeset diagram-div::-webkit-scrollbar-corner,.md-typeset div.arithmatex::-webkit-scrollbar-corner,.md-typeset div.diagram::-webkit-scrollbar-corner,.md-typeset div.mermaid::-webkit-scrollbar-corner,.md-typeset mermaid-div::-webkit-scrollbar-corner,.md-typeset pre.arithmatex::-webkit-scrollbar-corner,.md-typeset pre>code::-webkit-scrollbar-corner,.md-typeset__scrollwrap::-webkit-scrollbar-corner{background-color:transparent}.md-search__scrollwrap::-webkit-scrollbar-thumb,.md-sidebar__scrollwrap::-webkit-scrollbar-thumb,.md-typeset diagram-div::-webkit-scrollbar-thumb,.md-typeset div.arithmatex::-webkit-scrollbar-thumb,.md-typeset div.diagram::-webkit-scrollbar-thumb,.md-typeset div.mermaid::-webkit-scrollbar-thumb,.md-typeset mermaid-div::-webkit-scrollbar-thumb,.md-typeset pre.arithmatex::-webkit-scrollbar-thumb,.md-typeset pre>code::-webkit-scrollbar-thumb,.md-typeset__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover,.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover,.md-typeset diagram-div::-webkit-scrollbar-thumb:hover,.md-typeset div.arithmatex::-webkit-scrollbar-thumb:hover,.md-typeset div.diagram::-webkit-scrollbar-thumb:hover,.md-typeset div.mermaid::-webkit-scrollbar-thumb:hover,.md-typeset mermaid-div::-webkit-scrollbar-thumb:hover,.md-typeset pre.arithmatex::-webkit-scrollbar-thumb:hover,.md-typeset pre>code::-webkit-scrollbar-thumb:hover,.md-typeset__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}.md-search__scrollwrap:hover,.md-sidebar__scrollwrap:hover,.md-typeset diagram-div:hover,.md-typeset div.arithmatex:hover,.md-typeset div.diagram:hover,.md-typeset div.mermaid:hover,.md-typeset mermaid-div:hover,.md-typeset pre.arithmatex:hover,.md-typeset pre>code:hover,.md-typeset__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) transparent}@media screen and (max-width:59.9375em){.md-header-nav__scheme{padding-right:0}label[for=__search]{padding-left:0}[data-md-color-scheme=dracula] .md-nav__source{color:var(--md-text-color);background-color:var(--md-primary-fg-color--transparent)}[data-md-color-scheme=dracula] .md-nav .md-nav__title{color:var(--md-text-color);background-color:var(--md-header-bg-color);border-bottom:.05rem solid var(--md-primary-fg-color)}}@media screen and (max-width:44.9375em){.md-typeset>diagram-div{margin-right:-.8rem;margin-left:-.8rem}.md-typeset>.collapse-code{margin-right:-.8rem;margin-left:-.8rem}.md-typeset>.collapse-code label.collapse{left:.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels{padding-left:0}.md-content__inner>.tabbed-set .tabbed-labels{max-width:100%;margin:0;padding-inline-start:0;scroll-padding-inline-start:0}.md-content__inner>.tabbed-set .tabbed-labels::after{padding-inline-end:0;content:none}.md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{margin-inline-start:0;padding-inline-start:0}.md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{margin-inline-end:0;padding-inline-end:0}}@media screen and (max-width:76.1875em){[data-md-color-scheme=dracula] .md-nav--primary .md-nav__item--active>.md-nav__link:not(:hover){color:var(--md-primary-fg-color)}[data-md-color-scheme=dracula] .md-nav--primary .md-nav__title{color:var(--md-text-color);background-color:var(--md-header-bg-color);border-bottom:.05rem solid var(--md-primary-fg-color)}} diff --git a/assets/css/fonts.css b/assets/css/fonts.css new file mode 100644 index 00000000..486dc703 --- /dev/null +++ b/assets/css/fonts.css @@ -0,0 +1,33 @@ +/* Devleaks personal preference for heading and emphasis + * (matches some Obsidian Dracula Theme at https://draculatheme.com/contribute) + * + * Last updated: 25-MAR-2024 + * + * I apologize for illegal use of one of the nicest font I ever encountered. + * You can find it here: https://abcdinamo.com/typefaces/arizona. + * Requested the demo file and use it here. + * I simply cannot afford it. + * If you think I really , please send a kind mail and I'll remove it right away. + * I'm very respectful of copyright, own a valid licence of all software I use, + * I even bought fonts in the past, but I cannot afford the 1750€ to get the full set. + * + */ +@font-face { + font-family: "ABCArizona"; + src: url("../fonts/ABCArizonaPlusVariable-Trial.woff2") format('woff2-variations'); +} +:root { + --md-text-font: "ABCArizona"; + --md-text-font-family: "ABCArizona"; + --font-variation-weight: 400; + --font-variation-italic: 0; + --font-variation-serif: 0.2; +} +aside, body, input { + font-family: var(--md-text-font-family); + font-variation-settings: "wght" var(--font-variation-weight),"ital" var(--font-variation-italic),"SRFF" var(--font-variation-serif); +} +@font-face { + font-family: "Fira Mono"; + src: url("../fonts/FiraMono-Regular.woff2") format('woff2'); +} diff --git a/assets/css/image-align.css b/assets/css/image-align.css new file mode 100644 index 00000000..bf61a2ec --- /dev/null +++ b/assets/css/image-align.css @@ -0,0 +1,14 @@ +/* https://forum.obsidian.md/t/align-image/78050 */ +img[alt*="center"] { + display: block; + margin-left: auto; + margin-right: auto; +} + +img[alt*="right"] { + float:right; + clear:right; + margin-left: 1rem; + margin-bottom: 2px; + margin-top: 2px; +} \ No newline at end of file diff --git a/assets/fonts/ABCArizonaPlusVariable-Trial.woff2 b/assets/fonts/ABCArizonaPlusVariable-Trial.woff2 new file mode 100644 index 00000000..f9817031 Binary files /dev/null and b/assets/fonts/ABCArizonaPlusVariable-Trial.woff2 differ diff --git a/assets/fonts/FiraMono-Regular.woff2 b/assets/fonts/FiraMono-Regular.woff2 new file mode 100644 index 00000000..72978f68 Binary files /dev/null and b/assets/fonts/FiraMono-Regular.woff2 differ diff --git a/assets/images/android-chrome-192x192.png b/assets/images/android-chrome-192x192.png new file mode 100644 index 00000000..33331419 Binary files /dev/null and b/assets/images/android-chrome-192x192.png differ diff --git a/assets/images/android-chrome-512x512.png b/assets/images/android-chrome-512x512.png new file mode 100644 index 00000000..2c2da452 Binary files /dev/null and b/assets/images/android-chrome-512x512.png differ diff --git a/assets/images/apple-touch-icon.png b/assets/images/apple-touch-icon.png new file mode 100644 index 00000000..ba3f541a Binary files /dev/null and b/assets/images/apple-touch-icon.png differ diff --git a/assets/images/favicon-16x16.png b/assets/images/favicon-16x16.png new file mode 100644 index 00000000..c5493d6f Binary files /dev/null and b/assets/images/favicon-16x16.png differ diff --git a/assets/images/favicon-32x32.png b/assets/images/favicon-32x32.png new file mode 100644 index 00000000..952fe0fb Binary files /dev/null and b/assets/images/favicon-32x32.png differ diff --git a/assets/images/favicon.ico b/assets/images/favicon.ico new file mode 100644 index 00000000..fbd3151b Binary files /dev/null and b/assets/images/favicon.ico differ diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 00000000..26e14e3d Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/images/logo.png b/assets/images/logo.png new file mode 100644 index 00000000..6da875f9 Binary files /dev/null and b/assets/images/logo.png differ diff --git a/assets/images/site.webmanifest b/assets/images/site.webmanifest new file mode 100644 index 00000000..74ff12bc --- /dev/null +++ b/assets/images/site.webmanifest @@ -0,0 +1,20 @@ +{ + "name": "Cockpitdecks", + "short_name": "cockpitdecks", + "icons": + [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#101010", + "display": "standalone" +} \ No newline at end of file diff --git a/assets/javascripts/bundle.88dd0f4e.min.js b/assets/javascripts/bundle.88dd0f4e.min.js new file mode 100644 index 00000000..fb8f3109 --- /dev/null +++ b/assets/javascripts/bundle.88dd0f4e.min.js @@ -0,0 +1,16 @@ +"use strict";(()=>{var Wi=Object.create;var gr=Object.defineProperty;var Di=Object.getOwnPropertyDescriptor;var Vi=Object.getOwnPropertyNames,Vt=Object.getOwnPropertySymbols,Ni=Object.getPrototypeOf,yr=Object.prototype.hasOwnProperty,ao=Object.prototype.propertyIsEnumerable;var io=(e,t,r)=>t in e?gr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,$=(e,t)=>{for(var r in t||(t={}))yr.call(t,r)&&io(e,r,t[r]);if(Vt)for(var r of Vt(t))ao.call(t,r)&&io(e,r,t[r]);return e};var so=(e,t)=>{var r={};for(var o in e)yr.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&Vt)for(var o of Vt(e))t.indexOf(o)<0&&ao.call(e,o)&&(r[o]=e[o]);return r};var xr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var zi=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Vi(t))!yr.call(e,n)&&n!==r&&gr(e,n,{get:()=>t[n],enumerable:!(o=Di(t,n))||o.enumerable});return e};var Mt=(e,t,r)=>(r=e!=null?Wi(Ni(e)):{},zi(t||!e||!e.__esModule?gr(r,"default",{value:e,enumerable:!0}):r,e));var co=(e,t,r)=>new Promise((o,n)=>{var i=p=>{try{s(r.next(p))}catch(c){n(c)}},a=p=>{try{s(r.throw(p))}catch(c){n(c)}},s=p=>p.done?o(p.value):Promise.resolve(p.value).then(i,a);s((r=r.apply(e,t)).next())});var lo=xr((Er,po)=>{(function(e,t){typeof Er=="object"&&typeof po!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(Er,function(){"use strict";function e(r){var o=!0,n=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(k){return!!(k&&k!==document&&k.nodeName!=="HTML"&&k.nodeName!=="BODY"&&"classList"in k&&"contains"in k.classList)}function p(k){var ft=k.type,qe=k.tagName;return!!(qe==="INPUT"&&a[ft]&&!k.readOnly||qe==="TEXTAREA"&&!k.readOnly||k.isContentEditable)}function c(k){k.classList.contains("focus-visible")||(k.classList.add("focus-visible"),k.setAttribute("data-focus-visible-added",""))}function l(k){k.hasAttribute("data-focus-visible-added")&&(k.classList.remove("focus-visible"),k.removeAttribute("data-focus-visible-added"))}function f(k){k.metaKey||k.altKey||k.ctrlKey||(s(r.activeElement)&&c(r.activeElement),o=!0)}function u(k){o=!1}function d(k){s(k.target)&&(o||p(k.target))&&c(k.target)}function y(k){s(k.target)&&(k.target.classList.contains("focus-visible")||k.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(k.target))}function L(k){document.visibilityState==="hidden"&&(n&&(o=!0),X())}function X(){document.addEventListener("mousemove",J),document.addEventListener("mousedown",J),document.addEventListener("mouseup",J),document.addEventListener("pointermove",J),document.addEventListener("pointerdown",J),document.addEventListener("pointerup",J),document.addEventListener("touchmove",J),document.addEventListener("touchstart",J),document.addEventListener("touchend",J)}function te(){document.removeEventListener("mousemove",J),document.removeEventListener("mousedown",J),document.removeEventListener("mouseup",J),document.removeEventListener("pointermove",J),document.removeEventListener("pointerdown",J),document.removeEventListener("pointerup",J),document.removeEventListener("touchmove",J),document.removeEventListener("touchstart",J),document.removeEventListener("touchend",J)}function J(k){k.target.nodeName&&k.target.nodeName.toLowerCase()==="html"||(o=!1,te())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",L,!0),X(),r.addEventListener("focus",d,!0),r.addEventListener("blur",y,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var qr=xr((hy,On)=>{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var $a=/["'&<>]/;On.exports=Pa;function Pa(e){var t=""+e,r=$a.exec(t);if(!r)return t;var o,n="",i=0,a=0;for(i=r.index;i{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof It=="object"&&typeof Yr=="object"?Yr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof It=="object"?It.ClipboardJS=r():t.ClipboardJS=r()})(It,function(){return function(){var e={686:function(o,n,i){"use strict";i.d(n,{default:function(){return Ui}});var a=i(279),s=i.n(a),p=i(370),c=i.n(p),l=i(817),f=i.n(l);function u(V){try{return document.execCommand(V)}catch(A){return!1}}var d=function(A){var M=f()(A);return u("cut"),M},y=d;function L(V){var A=document.documentElement.getAttribute("dir")==="rtl",M=document.createElement("textarea");M.style.fontSize="12pt",M.style.border="0",M.style.padding="0",M.style.margin="0",M.style.position="absolute",M.style[A?"right":"left"]="-9999px";var F=window.pageYOffset||document.documentElement.scrollTop;return M.style.top="".concat(F,"px"),M.setAttribute("readonly",""),M.value=V,M}var X=function(A,M){var F=L(A);M.container.appendChild(F);var D=f()(F);return u("copy"),F.remove(),D},te=function(A){var M=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},F="";return typeof A=="string"?F=X(A,M):A instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(A==null?void 0:A.type)?F=X(A.value,M):(F=f()(A),u("copy")),F},J=te;function k(V){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?k=function(M){return typeof M}:k=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},k(V)}var ft=function(){var A=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},M=A.action,F=M===void 0?"copy":M,D=A.container,Y=A.target,$e=A.text;if(F!=="copy"&&F!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(Y!==void 0)if(Y&&k(Y)==="object"&&Y.nodeType===1){if(F==="copy"&&Y.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(F==="cut"&&(Y.hasAttribute("readonly")||Y.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if($e)return J($e,{container:D});if(Y)return F==="cut"?y(Y):J(Y,{container:D})},qe=ft;function Fe(V){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Fe=function(M){return typeof M}:Fe=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},Fe(V)}function ki(V,A){if(!(V instanceof A))throw new TypeError("Cannot call a class as a function")}function no(V,A){for(var M=0;M0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof D.action=="function"?D.action:this.defaultAction,this.target=typeof D.target=="function"?D.target:this.defaultTarget,this.text=typeof D.text=="function"?D.text:this.defaultText,this.container=Fe(D.container)==="object"?D.container:document.body}},{key:"listenClick",value:function(D){var Y=this;this.listener=c()(D,"click",function($e){return Y.onClick($e)})}},{key:"onClick",value:function(D){var Y=D.delegateTarget||D.currentTarget,$e=this.action(Y)||"copy",Dt=qe({action:$e,container:this.container,target:this.target(Y),text:this.text(Y)});this.emit(Dt?"success":"error",{action:$e,text:Dt,trigger:Y,clearSelection:function(){Y&&Y.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(D){return vr("action",D)}},{key:"defaultTarget",value:function(D){var Y=vr("target",D);if(Y)return document.querySelector(Y)}},{key:"defaultText",value:function(D){return vr("text",D)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(D){var Y=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return J(D,Y)}},{key:"cut",value:function(D){return y(D)}},{key:"isSupported",value:function(){var D=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],Y=typeof D=="string"?[D]:D,$e=!!document.queryCommandSupported;return Y.forEach(function(Dt){$e=$e&&!!document.queryCommandSupported(Dt)}),$e}}]),M}(s()),Ui=Fi},828:function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,p){for(;s&&s.nodeType!==n;){if(typeof s.matches=="function"&&s.matches(p))return s;s=s.parentNode}}o.exports=a},438:function(o,n,i){var a=i(828);function s(l,f,u,d,y){var L=c.apply(this,arguments);return l.addEventListener(u,L,y),{destroy:function(){l.removeEventListener(u,L,y)}}}function p(l,f,u,d,y){return typeof l.addEventListener=="function"?s.apply(null,arguments):typeof u=="function"?s.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(L){return s(L,f,u,d,y)}))}function c(l,f,u,d){return function(y){y.delegateTarget=a(y.target,f),y.delegateTarget&&d.call(l,y)}}o.exports=p},879:function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(o,n,i){var a=i(879),s=i(438);function p(u,d,y){if(!u&&!d&&!y)throw new Error("Missing required arguments");if(!a.string(d))throw new TypeError("Second argument must be a String");if(!a.fn(y))throw new TypeError("Third argument must be a Function");if(a.node(u))return c(u,d,y);if(a.nodeList(u))return l(u,d,y);if(a.string(u))return f(u,d,y);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(u,d,y){return u.addEventListener(d,y),{destroy:function(){u.removeEventListener(d,y)}}}function l(u,d,y){return Array.prototype.forEach.call(u,function(L){L.addEventListener(d,y)}),{destroy:function(){Array.prototype.forEach.call(u,function(L){L.removeEventListener(d,y)})}}}function f(u,d,y){return s(document.body,u,d,y)}o.exports=p},817:function(o){function n(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var p=window.getSelection(),c=document.createRange();c.selectNodeContents(i),p.removeAllRanges(),p.addRange(c),a=p.toString()}return a}o.exports=n},279:function(o){function n(){}n.prototype={on:function(i,a,s){var p=this.e||(this.e={});return(p[i]||(p[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var p=this;function c(){p.off(i,c),a.apply(s,arguments)}return c._=a,this.on(i,c,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),p=0,c=s.length;for(p;p0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function N(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],a;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(s){a={error:s}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(a)throw a.error}}return i}function q(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||p(d,L)})},y&&(n[d]=y(n[d])))}function p(d,y){try{c(o[d](y))}catch(L){u(i[0][3],L)}}function c(d){d.value instanceof nt?Promise.resolve(d.value.v).then(l,f):u(i[0][2],d)}function l(d){p("next",d)}function f(d){p("throw",d)}function u(d,y){d(y),i.shift(),i.length&&p(i[0][0],i[0][1])}}function uo(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof he=="function"?he(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(a){return new Promise(function(s,p){a=e[i](a),n(s,p,a.done,a.value)})}}function n(i,a,s,p){Promise.resolve(p).then(function(c){i({value:c,done:s})},a)}}function H(e){return typeof e=="function"}function ut(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var zt=ut(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Qe(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ue=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=he(a),p=s.next();!p.done;p=s.next()){var c=p.value;c.remove(this)}}catch(L){t={error:L}}finally{try{p&&!p.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var l=this.initialTeardown;if(H(l))try{l()}catch(L){i=L instanceof zt?L.errors:[L]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=he(f),d=u.next();!d.done;d=u.next()){var y=d.value;try{ho(y)}catch(L){i=i!=null?i:[],L instanceof zt?i=q(q([],N(i)),N(L.errors)):i.push(L)}}}catch(L){o={error:L}}finally{try{d&&!d.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new zt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)ho(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Qe(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Qe(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Tr=Ue.EMPTY;function qt(e){return e instanceof Ue||e&&"closed"in e&&H(e.remove)&&H(e.add)&&H(e.unsubscribe)}function ho(e){H(e)?e():e.unsubscribe()}var Pe={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var dt={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,a=n.isStopped,s=n.observers;return i||a?Tr:(this.currentObservers=null,s.push(r),new Ue(function(){o.currentObservers=null,Qe(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,a=o.isStopped;n?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new j;return r.source=this,r},t.create=function(r,o){return new To(r,o)},t}(j);var To=function(e){oe(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:Tr},t}(g);var _r=function(e){oe(t,e);function t(r){var o=e.call(this)||this;return o._value=r,o}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var o=e.prototype._subscribe.call(this,r);return!o.closed&&r.next(this._value),o},t.prototype.getValue=function(){var r=this,o=r.hasError,n=r.thrownError,i=r._value;if(o)throw n;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t}(g);var At={now:function(){return(At.delegate||Date).now()},delegate:void 0};var Ct=function(e){oe(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=At);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,a=o._infiniteTimeWindow,s=o._timestampProvider,p=o._windowTime;n||(i.push(r),!a&&i.push(s.now()+p)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,a=n._buffer,s=a.slice(),p=0;p0?e.prototype.schedule.call(this,r,o):(this.delay=o,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,o){return o>0||this.closed?e.prototype.execute.call(this,r,o):this._execute(r,o)},t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!=null&&n>0||n==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.flush(this),0)},t}(gt);var Lo=function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t}(yt);var kr=new Lo(Oo);var Mo=function(e){oe(t,e);function t(r,o){var n=e.call(this,r,o)||this;return n.scheduler=r,n.work=o,n}return t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!==null&&n>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=vt.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var a=r.actions;o!=null&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==o&&(vt.cancelAnimationFrame(o),r._scheduled=void 0)},t}(gt);var _o=function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o=this._scheduled;this._scheduled=void 0;var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t}(yt);var me=new _o(Mo);var S=new j(function(e){return e.complete()});function Yt(e){return e&&H(e.schedule)}function Hr(e){return e[e.length-1]}function Xe(e){return H(Hr(e))?e.pop():void 0}function ke(e){return Yt(Hr(e))?e.pop():void 0}function Bt(e,t){return typeof Hr(e)=="number"?e.pop():t}var xt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Gt(e){return H(e==null?void 0:e.then)}function Jt(e){return H(e[bt])}function Xt(e){return Symbol.asyncIterator&&H(e==null?void 0:e[Symbol.asyncIterator])}function Zt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Zi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var er=Zi();function tr(e){return H(e==null?void 0:e[er])}function rr(e){return fo(this,arguments,function(){var r,o,n,i;return Nt(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,nt(r.read())];case 3:return o=a.sent(),n=o.value,i=o.done,i?[4,nt(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,nt(n)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function or(e){return H(e==null?void 0:e.getReader)}function U(e){if(e instanceof j)return e;if(e!=null){if(Jt(e))return ea(e);if(xt(e))return ta(e);if(Gt(e))return ra(e);if(Xt(e))return Ao(e);if(tr(e))return oa(e);if(or(e))return na(e)}throw Zt(e)}function ea(e){return new j(function(t){var r=e[bt]();if(H(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function ta(e){return new j(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?b(function(n,i){return e(n,i,o)}):le,Te(1),r?De(t):Qo(function(){return new ir}))}}function jr(e){return e<=0?function(){return S}:E(function(t,r){var o=[];t.subscribe(T(r,function(n){o.push(n),e=2,!0))}function pe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new g}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,p=s===void 0?!0:s;return function(c){var l,f,u,d=0,y=!1,L=!1,X=function(){f==null||f.unsubscribe(),f=void 0},te=function(){X(),l=u=void 0,y=L=!1},J=function(){var k=l;te(),k==null||k.unsubscribe()};return E(function(k,ft){d++,!L&&!y&&X();var qe=u=u!=null?u:r();ft.add(function(){d--,d===0&&!L&&!y&&(f=Ur(J,p))}),qe.subscribe(ft),!l&&d>0&&(l=new at({next:function(Fe){return qe.next(Fe)},error:function(Fe){L=!0,X(),f=Ur(te,n,Fe),qe.error(Fe)},complete:function(){y=!0,X(),f=Ur(te,a),qe.complete()}}),U(k).subscribe(l))})(c)}}function Ur(e,t){for(var r=[],o=2;oe.next(document)),e}function P(e,t=document){return Array.from(t.querySelectorAll(e))}function R(e,t=document){let r=fe(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function fe(e,t=document){return t.querySelector(e)||void 0}function Ie(){var e,t,r,o;return(o=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?o:void 0}var wa=O(h(document.body,"focusin"),h(document.body,"focusout")).pipe(_e(1),Q(void 0),m(()=>Ie()||document.body),G(1));function et(e){return wa.pipe(m(t=>e.contains(t)),K())}function $t(e,t){return C(()=>O(h(e,"mouseenter").pipe(m(()=>!0)),h(e,"mouseleave").pipe(m(()=>!1))).pipe(t?Ht(r=>Le(+!r*t)):le,Q(e.matches(":hover"))))}function Jo(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Jo(e,r)}function x(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)Jo(o,n);return o}function sr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function Tt(e){let t=x("script",{src:e});return C(()=>(document.head.appendChild(t),O(h(t,"load"),h(t,"error").pipe(v(()=>$r(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),_(()=>document.head.removeChild(t)),Te(1))))}var Xo=new g,Ta=C(()=>typeof ResizeObserver=="undefined"?Tt("https://unpkg.com/resize-observer-polyfill"):I(void 0)).pipe(m(()=>new ResizeObserver(e=>e.forEach(t=>Xo.next(t)))),v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function ce(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ge(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return Ta.pipe(w(r=>r.observe(t)),v(r=>Xo.pipe(b(o=>o.target===t),_(()=>r.unobserve(t)))),m(()=>ce(e)),Q(ce(e)))}function St(e){return{width:e.scrollWidth,height:e.scrollHeight}}function cr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function Zo(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function Ve(e){return{x:e.offsetLeft,y:e.offsetTop}}function en(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function tn(e){return O(h(window,"load"),h(window,"resize")).pipe(Me(0,me),m(()=>Ve(e)),Q(Ve(e)))}function pr(e){return{x:e.scrollLeft,y:e.scrollTop}}function Ne(e){return O(h(e,"scroll"),h(window,"scroll"),h(window,"resize")).pipe(Me(0,me),m(()=>pr(e)),Q(pr(e)))}var rn=new g,Sa=C(()=>I(new IntersectionObserver(e=>{for(let t of e)rn.next(t)},{threshold:0}))).pipe(v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function tt(e){return Sa.pipe(w(t=>t.observe(e)),v(t=>rn.pipe(b(({target:r})=>r===e),_(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function on(e,t=16){return Ne(e).pipe(m(({y:r})=>{let o=ce(e),n=St(e);return r>=n.height-o.height-t}),K())}var lr={drawer:R("[data-md-toggle=drawer]"),search:R("[data-md-toggle=search]")};function nn(e){return lr[e].checked}function Je(e,t){lr[e].checked!==t&&lr[e].click()}function ze(e){let t=lr[e];return h(t,"change").pipe(m(()=>t.checked),Q(t.checked))}function Oa(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function La(){return O(h(window,"compositionstart").pipe(m(()=>!0)),h(window,"compositionend").pipe(m(()=>!1))).pipe(Q(!1))}function an(){let e=h(window,"keydown").pipe(b(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:nn("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),b(({mode:t,type:r})=>{if(t==="global"){let o=Ie();if(typeof o!="undefined")return!Oa(o,r)}return!0}),pe());return La().pipe(v(t=>t?S:e))}function ye(){return new URL(location.href)}function lt(e,t=!1){if(B("navigation.instant")&&!t){let r=x("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function sn(){return new g}function cn(){return location.hash.slice(1)}function pn(e){let t=x("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Ma(e){return O(h(window,"hashchange"),e).pipe(m(cn),Q(cn()),b(t=>t.length>0),G(1))}function ln(e){return Ma(e).pipe(m(t=>fe(`[id="${t}"]`)),b(t=>typeof t!="undefined"))}function Pt(e){let t=matchMedia(e);return ar(r=>t.addListener(()=>r(t.matches))).pipe(Q(t.matches))}function mn(){let e=matchMedia("print");return O(h(window,"beforeprint").pipe(m(()=>!0)),h(window,"afterprint").pipe(m(()=>!1))).pipe(Q(e.matches))}function Nr(e,t){return e.pipe(v(r=>r?t():S))}function zr(e,t){return new j(r=>{let o=new XMLHttpRequest;return o.open("GET",`${e}`),o.responseType="blob",o.addEventListener("load",()=>{o.status>=200&&o.status<300?(r.next(o.response),r.complete()):r.error(new Error(o.statusText))}),o.addEventListener("error",()=>{r.error(new Error("Network error"))}),o.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(o.addEventListener("progress",n=>{var i;if(n.lengthComputable)t.progress$.next(n.loaded/n.total*100);else{let a=(i=o.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(n.loaded/+a*100)}}),t.progress$.next(5)),o.send(),()=>o.abort()})}function je(e,t){return zr(e,t).pipe(v(r=>r.text()),m(r=>JSON.parse(r)),G(1))}function fn(e,t){let r=new DOMParser;return zr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/html")),G(1))}function un(e,t){let r=new DOMParser;return zr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),G(1))}function dn(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function hn(){return O(h(window,"scroll",{passive:!0}),h(window,"resize",{passive:!0})).pipe(m(dn),Q(dn()))}function bn(){return{width:innerWidth,height:innerHeight}}function vn(){return h(window,"resize",{passive:!0}).pipe(m(bn),Q(bn()))}function gn(){return z([hn(),vn()]).pipe(m(([e,t])=>({offset:e,size:t})),G(1))}function mr(e,{viewport$:t,header$:r}){let o=t.pipe(ee("size")),n=z([o,r]).pipe(m(()=>Ve(e)));return z([r,t,n]).pipe(m(([{height:i},{offset:a,size:s},{x:p,y:c}])=>({offset:{x:a.x-p,y:a.y-c+i},size:s})))}function _a(e){return h(e,"message",t=>t.data)}function Aa(e){let t=new g;return t.subscribe(r=>e.postMessage(r)),t}function yn(e,t=new Worker(e)){let r=_a(t),o=Aa(t),n=new g;n.subscribe(o);let i=o.pipe(Z(),ie(!0));return n.pipe(Z(),Re(r.pipe(W(i))),pe())}var Ca=R("#__config"),Ot=JSON.parse(Ca.textContent);Ot.base=`${new URL(Ot.base,ye())}`;function xe(){return Ot}function B(e){return Ot.features.includes(e)}function Ee(e,t){return typeof t!="undefined"?Ot.translations[e].replace("#",t.toString()):Ot.translations[e]}function Se(e,t=document){return R(`[data-md-component=${e}]`,t)}function ae(e,t=document){return P(`[data-md-component=${e}]`,t)}function ka(e){let t=R(".md-typeset > :first-child",e);return h(t,"click",{once:!0}).pipe(m(()=>R(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function xn(e){if(!B("announce.dismiss")||!e.childElementCount)return S;if(!e.hidden){let t=R(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return C(()=>{let t=new g;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),ka(e).pipe(w(r=>t.next(r)),_(()=>t.complete()),m(r=>$({ref:e},r)))})}function Ha(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function En(e,t){let r=new g;return r.subscribe(({hidden:o})=>{e.hidden=o}),Ha(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))}function Rt(e,t){return t==="inline"?x("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"})):x("div",{class:"md-tooltip",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"}))}function wn(...e){return x("div",{class:"md-tooltip2",role:"tooltip"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function Tn(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return x("aside",{class:"md-annotation",tabIndex:0},Rt(t),x("a",{href:r,class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}else return x("aside",{class:"md-annotation",tabIndex:0},Rt(t),x("span",{class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}function Sn(e){return x("button",{class:"md-clipboard md-icon",title:Ee("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}var Ln=Mt(qr());function Qr(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(p=>!e.terms[p]).reduce((p,c)=>[...p,x("del",null,(0,Ln.default)(c))," "],[]).slice(0,-1),i=xe(),a=new URL(e.location,i.base);B("search.highlight")&&a.searchParams.set("h",Object.entries(e.terms).filter(([,p])=>p).reduce((p,[c])=>`${p} ${c}`.trim(),""));let{tags:s}=xe();return x("a",{href:`${a}`,class:"md-search-result__link",tabIndex:-1},x("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&x("div",{class:"md-search-result__icon md-icon"}),r>0&&x("h1",null,e.title),r<=0&&x("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&x("nav",{class:"md-tags"},e.tags.map(p=>{let c=s?p in s?`md-tag-icon md-tag--${s[p]}`:"md-tag-icon":"";return x("span",{class:`md-tag ${c}`},p)})),o>0&&n.length>0&&x("p",{class:"md-search-result__terms"},Ee("search.result.term.missing"),": ",...n)))}function Mn(e){let t=e[0].score,r=[...e],o=xe(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),a=r.findIndex(l=>l.scoreQr(l,1)),...p.length?[x("details",{class:"md-search-result__more"},x("summary",{tabIndex:-1},x("div",null,p.length>0&&p.length===1?Ee("search.result.more.one"):Ee("search.result.more.other",p.length))),...p.map(l=>Qr(l,1)))]:[]];return x("li",{class:"md-search-result__item"},c)}function _n(e){return x("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>x("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?sr(r):r)))}function Kr(e){let t=`tabbed-control tabbed-control--${e}`;return x("div",{class:t,hidden:!0},x("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function An(e){return x("div",{class:"md-typeset__scrollwrap"},x("div",{class:"md-typeset__table"},e))}function Ra(e){var o;let t=xe(),r=new URL(`../${e.version}/`,t.base);return x("li",{class:"md-version__item"},x("a",{href:`${r}`,class:"md-version__link"},e.title,((o=t.version)==null?void 0:o.alias)&&e.aliases.length>0&&x("span",{class:"md-version__alias"},e.aliases[0])))}function Cn(e,t){var o;let r=xe();return e=e.filter(n=>{var i;return!((i=n.properties)!=null&&i.hidden)}),x("div",{class:"md-version"},x("button",{class:"md-version__current","aria-label":Ee("select.version")},t.title,((o=r.version)==null?void 0:o.alias)&&t.aliases.length>0&&x("span",{class:"md-version__alias"},t.aliases[0])),x("ul",{class:"md-version__list"},e.map(Ra)))}var Ia=0;function ja(e){let t=z([et(e),$t(e)]).pipe(m(([o,n])=>o||n),K()),r=C(()=>Zo(e)).pipe(ne(Ne),pt(1),He(t),m(()=>en(e)));return t.pipe(Ae(o=>o),v(()=>z([t,r])),m(([o,n])=>({active:o,offset:n})),pe())}function Fa(e,t){let{content$:r,viewport$:o}=t,n=`__tooltip2_${Ia++}`;return C(()=>{let i=new g,a=new _r(!1);i.pipe(Z(),ie(!1)).subscribe(a);let s=a.pipe(Ht(c=>Le(+!c*250,kr)),K(),v(c=>c?r:S),w(c=>c.id=n),pe());z([i.pipe(m(({active:c})=>c)),s.pipe(v(c=>$t(c,250)),Q(!1))]).pipe(m(c=>c.some(l=>l))).subscribe(a);let p=a.pipe(b(c=>c),re(s,o),m(([c,l,{size:f}])=>{let u=e.getBoundingClientRect(),d=u.width/2;if(l.role==="tooltip")return{x:d,y:8+u.height};if(u.y>=f.height/2){let{height:y}=ce(l);return{x:d,y:-16-y}}else return{x:d,y:16+u.height}}));return z([s,i,p]).subscribe(([c,{offset:l},f])=>{c.style.setProperty("--md-tooltip-host-x",`${l.x}px`),c.style.setProperty("--md-tooltip-host-y",`${l.y}px`),c.style.setProperty("--md-tooltip-x",`${f.x}px`),c.style.setProperty("--md-tooltip-y",`${f.y}px`),c.classList.toggle("md-tooltip2--top",f.y<0),c.classList.toggle("md-tooltip2--bottom",f.y>=0)}),a.pipe(b(c=>c),re(s,(c,l)=>l),b(c=>c.role==="tooltip")).subscribe(c=>{let l=ce(R(":scope > *",c));c.style.setProperty("--md-tooltip-width",`${l.width}px`),c.style.setProperty("--md-tooltip-tail","0px")}),a.pipe(K(),ve(me),re(s)).subscribe(([c,l])=>{l.classList.toggle("md-tooltip2--active",c)}),z([a.pipe(b(c=>c)),s]).subscribe(([c,l])=>{l.role==="dialog"?(e.setAttribute("aria-controls",n),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",n)}),a.pipe(b(c=>!c)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),ja(e).pipe(w(c=>i.next(c)),_(()=>i.complete()),m(c=>$({ref:e},c)))})}function mt(e,{viewport$:t},r=document.body){return Fa(e,{content$:new j(o=>{let n=e.title,i=wn(n);return o.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",n)}}),viewport$:t})}function Ua(e,t){let r=C(()=>z([tn(e),Ne(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:a,height:s}=ce(e);return{x:o-i.x+a/2,y:n-i.y+s/2}}));return et(e).pipe(v(o=>r.pipe(m(n=>({active:o,offset:n})),Te(+!o||1/0))))}function kn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return C(()=>{let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({offset:s}){e.style.setProperty("--md-tooltip-x",`${s.x}px`),e.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),tt(e).pipe(W(a)).subscribe(s=>{e.toggleAttribute("data-md-visible",s)}),O(i.pipe(b(({active:s})=>s)),i.pipe(_e(250),b(({active:s})=>!s))).subscribe({next({active:s}){s?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe(Me(16,me)).subscribe(({active:s})=>{o.classList.toggle("md-tooltip--active",s)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:s})=>s)).subscribe({next(s){s?e.style.setProperty("--md-tooltip-0",`${-s}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),h(n,"click").pipe(W(a),b(s=>!(s.metaKey||s.ctrlKey))).subscribe(s=>{s.stopPropagation(),s.preventDefault()}),h(n,"mousedown").pipe(W(a),re(i)).subscribe(([s,{active:p}])=>{var c;if(s.button!==0||s.metaKey||s.ctrlKey)s.preventDefault();else if(p){s.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(c=Ie())==null||c.blur()}}),r.pipe(W(a),b(s=>s===o),Ge(125)).subscribe(()=>e.focus()),Ua(e,t).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function Wa(e){return e.tagName==="CODE"?P(".c, .c1, .cm",e):[e]}function Da(e){let t=[];for(let r of Wa(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let a;for(;a=/(\(\d+\))(!)?/.exec(i.textContent);){let[,s,p]=a;if(typeof p=="undefined"){let c=i.splitText(a.index);i=c.splitText(s.length),t.push(c)}else{i.textContent=s,t.push(i);break}}}}return t}function Hn(e,t){t.append(...Array.from(e.childNodes))}function fr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,a=new Map;for(let s of Da(t)){let[,p]=s.textContent.match(/\((\d+)\)/);fe(`:scope > li:nth-child(${p})`,e)&&(a.set(p,Tn(p,i)),s.replaceWith(a.get(p)))}return a.size===0?S:C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=[];for(let[l,f]of a)c.push([R(".md-typeset",f),R(`:scope > li:nth-child(${l})`,e)]);return o.pipe(W(p)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of c)l?Hn(f,u):Hn(u,f)}),O(...[...a].map(([,l])=>kn(l,t,{target$:r}))).pipe(_(()=>s.complete()),pe())})}function $n(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return $n(t)}}function Pn(e,t){return C(()=>{let r=$n(e);return typeof r!="undefined"?fr(r,e,t):S})}var Rn=Mt(Br());var Va=0;function In(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return In(t)}}function Na(e){return ge(e).pipe(m(({width:t})=>({scrollable:St(e).width>t})),ee("scrollable"))}function jn(e,t){let{matches:r}=matchMedia("(hover)"),o=C(()=>{let n=new g,i=n.pipe(jr(1));n.subscribe(({scrollable:c})=>{c&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let a=[];if(Rn.default.isSupported()&&(e.closest(".copy")||B("content.code.copy")&&!e.closest(".no-copy"))){let c=e.closest("pre");c.id=`__code_${Va++}`;let l=Sn(c.id);c.insertBefore(l,e),B("content.tooltips")&&a.push(mt(l,{viewport$}))}let s=e.closest(".highlight");if(s instanceof HTMLElement){let c=In(s);if(typeof c!="undefined"&&(s.classList.contains("annotate")||B("content.code.annotate"))){let l=fr(c,e,t);a.push(ge(s).pipe(W(i),m(({width:f,height:u})=>f&&u),K(),v(f=>f?l:S)))}}return P(":scope > span[id]",e).length&&e.classList.add("md-code__content"),Na(e).pipe(w(c=>n.next(c)),_(()=>n.complete()),m(c=>$({ref:e},c)),Re(...a))});return B("content.lazy")?tt(e).pipe(b(n=>n),Te(1),v(()=>o)):o}function za(e,{target$:t,print$:r}){let o=!0;return O(t.pipe(m(n=>n.closest("details:not([open])")),b(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(b(n=>n||!o),w(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Fn(e,t){return C(()=>{let r=new g;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),za(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}var Un=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.flowchartTitleText{fill:var(--md-mermaid-label-fg-color)}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel p,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel p{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color);stroke-width:.05rem}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}.classDiagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs #classDiagram-compositionEnd,defs #classDiagram-compositionStart,defs #classDiagram-dependencyEnd,defs #classDiagram-dependencyStart,defs #classDiagram-extensionEnd,defs #classDiagram-extensionStart{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs #classDiagram-aggregationEnd,defs #classDiagram-aggregationStart{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}.statediagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}a .nodeLabel{text-decoration:underline}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}.entityTitleText{fill:var(--md-mermaid-label-fg-color)}.attributeBoxEven,.attributeBoxOdd{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityBox{fill:var(--md-mermaid-label-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityLabel{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.relationshipLabelBox{fill:var(--md-mermaid-label-bg-color);fill-opacity:1;background-color:var(--md-mermaid-label-bg-color);opacity:1}.relationshipLabel{fill:var(--md-mermaid-label-fg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs #ONE_OR_MORE_END *,defs #ONE_OR_MORE_START *,defs #ONLY_ONE_END *,defs #ONLY_ONE_START *,defs #ZERO_OR_MORE_END *,defs #ZERO_OR_MORE_START *,defs #ZERO_OR_ONE_END *,defs #ZERO_OR_ONE_START *{stroke:var(--md-mermaid-edge-color)!important}defs #ZERO_OR_MORE_END circle,defs #ZERO_OR_MORE_START circle{fill:var(--md-mermaid-label-bg-color)}text:not([class]):last-child{fill:var(--md-mermaid-label-fg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var Gr,Qa=0;function Ka(){return typeof mermaid=="undefined"||mermaid instanceof Element?Tt("https://unpkg.com/mermaid@11/dist/mermaid.min.js"):I(void 0)}function Wn(e){return e.classList.remove("mermaid"),Gr||(Gr=Ka().pipe(w(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Un,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),G(1))),Gr.subscribe(()=>co(this,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${Qa++}`,r=x("div",{class:"mermaid"}),o=e.textContent,{svg:n,fn:i}=yield mermaid.render(t,o),a=r.attachShadow({mode:"closed"});a.innerHTML=n,e.replaceWith(r),i==null||i(a)})),Gr.pipe(m(()=>({ref:e})))}var Dn=x("table");function Vn(e){return e.replaceWith(Dn),Dn.replaceWith(An(e)),I({ref:e})}function Ya(e){let t=e.find(r=>r.checked)||e[0];return O(...e.map(r=>h(r,"change").pipe(m(()=>R(`label[for="${r.id}"]`))))).pipe(Q(R(`label[for="${t.id}"]`)),m(r=>({active:r})))}function Nn(e,{viewport$:t,target$:r}){let o=R(".tabbed-labels",e),n=P(":scope > input",e),i=Kr("prev");e.append(i);let a=Kr("next");return e.append(a),C(()=>{let s=new g,p=s.pipe(Z(),ie(!0));z([s,ge(e),tt(e)]).pipe(W(p),Me(1,me)).subscribe({next([{active:c},l]){let f=Ve(c),{width:u}=ce(c);e.style.setProperty("--md-indicator-x",`${f.x}px`),e.style.setProperty("--md-indicator-width",`${u}px`);let d=pr(o);(f.xd.x+l.width)&&o.scrollTo({left:Math.max(0,f.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),z([Ne(o),ge(o)]).pipe(W(p)).subscribe(([c,l])=>{let f=St(o);i.hidden=c.x<16,a.hidden=c.x>f.width-l.width-16}),O(h(i,"click").pipe(m(()=>-1)),h(a,"click").pipe(m(()=>1))).pipe(W(p)).subscribe(c=>{let{width:l}=ce(o);o.scrollBy({left:l*c,behavior:"smooth"})}),r.pipe(W(p),b(c=>n.includes(c))).subscribe(c=>c.click()),o.classList.add("tabbed-labels--linked");for(let c of n){let l=R(`label[for="${c.id}"]`);l.replaceChildren(x("a",{href:`#${l.htmlFor}`,tabIndex:-1},...Array.from(l.childNodes))),h(l.firstElementChild,"click").pipe(W(p),b(f=>!(f.metaKey||f.ctrlKey)),w(f=>{f.preventDefault(),f.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${l.htmlFor}`),l.click()})}return B("content.tabs.link")&&s.pipe(Ce(1),re(t)).subscribe(([{active:c},{offset:l}])=>{let f=c.innerText.trim();if(c.hasAttribute("data-md-switching"))c.removeAttribute("data-md-switching");else{let u=e.offsetTop-l.y;for(let y of P("[data-tabs]"))for(let L of P(":scope > input",y)){let X=R(`label[for="${L.id}"]`);if(X!==c&&X.innerText.trim()===f){X.setAttribute("data-md-switching",""),L.click();break}}window.scrollTo({top:e.offsetTop-u});let d=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([f,...d])])}}),s.pipe(W(p)).subscribe(()=>{for(let c of P("audio, video",e))c.pause()}),Ya(n).pipe(w(c=>s.next(c)),_(()=>s.complete()),m(c=>$({ref:e},c)))}).pipe(Ke(se))}function zn(e,{viewport$:t,target$:r,print$:o}){return O(...P(".annotate:not(.highlight)",e).map(n=>Pn(n,{target$:r,print$:o})),...P("pre:not(.mermaid) > code",e).map(n=>jn(n,{target$:r,print$:o})),...P("pre.mermaid",e).map(n=>Wn(n)),...P("table:not([class])",e).map(n=>Vn(n)),...P("details",e).map(n=>Fn(n,{target$:r,print$:o})),...P("[data-tabs]",e).map(n=>Nn(n,{viewport$:t,target$:r})),...P("[title]",e).filter(()=>B("content.tooltips")).map(n=>mt(n,{viewport$:t})))}function Ba(e,{alert$:t}){return t.pipe(v(r=>O(I(!0),I(!1).pipe(Ge(2e3))).pipe(m(o=>({message:r,active:o})))))}function qn(e,t){let r=R(".md-typeset",e);return C(()=>{let o=new g;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),Ba(e,t).pipe(w(n=>o.next(n)),_(()=>o.complete()),m(n=>$({ref:e},n)))})}var Ga=0;function Ja(e,t){document.body.append(e);let{width:r}=ce(e);e.style.setProperty("--md-tooltip-width",`${r}px`),e.remove();let o=cr(t),n=typeof o!="undefined"?Ne(o):I({x:0,y:0}),i=O(et(t),$t(t)).pipe(K());return z([i,n]).pipe(m(([a,s])=>{let{x:p,y:c}=Ve(t),l=ce(t),f=t.closest("table");return f&&t.parentElement&&(p+=f.offsetLeft+t.parentElement.offsetLeft,c+=f.offsetTop+t.parentElement.offsetTop),{active:a,offset:{x:p-s.x+l.width/2-r/2,y:c-s.y+l.height+8}}}))}function Qn(e){let t=e.title;if(!t.length)return S;let r=`__tooltip_${Ga++}`,o=Rt(r,"inline"),n=R(".md-typeset",o);return n.innerHTML=t,C(()=>{let i=new g;return i.subscribe({next({offset:a}){o.style.setProperty("--md-tooltip-x",`${a.x}px`),o.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){o.style.removeProperty("--md-tooltip-x"),o.style.removeProperty("--md-tooltip-y")}}),O(i.pipe(b(({active:a})=>a)),i.pipe(_e(250),b(({active:a})=>!a))).subscribe({next({active:a}){a?(e.insertAdjacentElement("afterend",o),e.setAttribute("aria-describedby",r),e.removeAttribute("title")):(o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t))},complete(){o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t)}}),i.pipe(Me(16,me)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?o.style.setProperty("--md-tooltip-0",`${-a}px`):o.style.removeProperty("--md-tooltip-0")},complete(){o.style.removeProperty("--md-tooltip-0")}}),Ja(o,e).pipe(w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))}).pipe(Ke(se))}function Xa({viewport$:e}){if(!B("header.autohide"))return I(!1);let t=e.pipe(m(({offset:{y:n}})=>n),Be(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),K()),o=ze("search");return z([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),K(),v(n=>n?r:I(!1)),Q(!1))}function Kn(e,t){return C(()=>z([ge(e),Xa(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),K((r,o)=>r.height===o.height&&r.hidden===o.hidden),G(1))}function Yn(e,{header$:t,main$:r}){return C(()=>{let o=new g,n=o.pipe(Z(),ie(!0));o.pipe(ee("active"),He(t)).subscribe(([{active:a},{hidden:s}])=>{e.classList.toggle("md-header--shadow",a&&!s),e.hidden=s});let i=ue(P("[title]",e)).pipe(b(()=>B("content.tooltips")),ne(a=>Qn(a)));return r.subscribe(o),t.pipe(W(n),m(a=>$({ref:e},a)),Re(i.pipe(W(n))))})}function Za(e,{viewport$:t,header$:r}){return mr(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=ce(e);return{active:o>=n}}),ee("active"))}function Bn(e,t){return C(()=>{let r=new g;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=fe(".md-content h1");return typeof o=="undefined"?S:Za(o,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))})}function Gn(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),K()),n=o.pipe(v(()=>ge(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),ee("bottom"))));return z([o,n,t]).pipe(m(([i,{top:a,bottom:s},{offset:{y:p},size:{height:c}}])=>(c=Math.max(0,c-Math.max(0,a-p,i)-Math.max(0,c+p-s)),{offset:a-i,height:c,active:a-i<=p})),K((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function es(e){let t=__md_get("__palette")||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return I(...e).pipe(ne(o=>h(o,"change").pipe(m(()=>o))),Q(e[r]),m(o=>({index:e.indexOf(o),color:{media:o.getAttribute("data-md-color-media"),scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),G(1))}function Jn(e){let t=P("input",e),r=x("meta",{name:"theme-color"});document.head.appendChild(r);let o=x("meta",{name:"color-scheme"});document.head.appendChild(o);let n=Pt("(prefers-color-scheme: light)");return C(()=>{let i=new g;return i.subscribe(a=>{if(document.body.setAttribute("data-md-color-switching",""),a.color.media==="(prefers-color-scheme)"){let s=matchMedia("(prefers-color-scheme: light)"),p=document.querySelector(s.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");a.color.scheme=p.getAttribute("data-md-color-scheme"),a.color.primary=p.getAttribute("data-md-color-primary"),a.color.accent=p.getAttribute("data-md-color-accent")}for(let[s,p]of Object.entries(a.color))document.body.setAttribute(`data-md-color-${s}`,p);for(let s=0;sa.key==="Enter"),re(i,(a,s)=>s)).subscribe(({index:a})=>{a=(a+1)%t.length,t[a].click(),t[a].focus()}),i.pipe(m(()=>{let a=Se("header"),s=window.getComputedStyle(a);return o.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(p=>(+p).toString(16).padStart(2,"0")).join("")})).subscribe(a=>r.content=`#${a}`),i.pipe(ve(se)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),es(t).pipe(W(n.pipe(Ce(1))),ct(),w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))})}function Xn(e,{progress$:t}){return C(()=>{let r=new g;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(w(o=>r.next({value:o})),_(()=>r.complete()),m(o=>({ref:e,value:o})))})}var Jr=Mt(Br());function ts(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function Zn({alert$:e}){Jr.default.isSupported()&&new j(t=>{new Jr.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||ts(R(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(w(t=>{t.trigger.focus()}),m(()=>Ee("clipboard.copied"))).subscribe(e)}function ei(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,e}function rs(e,t){let r=new Map;for(let o of P("url",e)){let n=R("loc",o),i=[ei(new URL(n.textContent),t)];r.set(`${i[0]}`,i);for(let a of P("[rel=alternate]",o)){let s=a.getAttribute("href");s!=null&&i.push(ei(new URL(s),t))}}return r}function ur(e){return un(new URL("sitemap.xml",e)).pipe(m(t=>rs(t,new URL(e))),de(()=>I(new Map)))}function os(e,t){if(!(e.target instanceof Element))return S;let r=e.target.closest("a");if(r===null)return S;if(r.target||e.metaKey||e.ctrlKey)return S;let o=new URL(r.href);return o.search=o.hash="",t.has(`${o}`)?(e.preventDefault(),I(new URL(r.href))):S}function ti(e){let t=new Map;for(let r of P(":scope > *",e.head))t.set(r.outerHTML,r);return t}function ri(e){for(let t of P("[href], [src]",e))for(let r of["href","src"]){let o=t.getAttribute(r);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){t[r]=t[r];break}}return I(e)}function ns(e){for(let o of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...B("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let n=fe(o),i=fe(o,e);typeof n!="undefined"&&typeof i!="undefined"&&n.replaceWith(i)}let t=ti(document);for(let[o,n]of ti(e))t.has(o)?t.delete(o):document.head.appendChild(n);for(let o of t.values()){let n=o.getAttribute("name");n!=="theme-color"&&n!=="color-scheme"&&o.remove()}let r=Se("container");return We(P("script",r)).pipe(v(o=>{let n=e.createElement("script");if(o.src){for(let i of o.getAttributeNames())n.setAttribute(i,o.getAttribute(i));return o.replaceWith(n),new j(i=>{n.onload=()=>i.complete()})}else return n.textContent=o.textContent,o.replaceWith(n),S}),Z(),ie(document))}function oi({location$:e,viewport$:t,progress$:r}){let o=xe();if(location.protocol==="file:")return S;let n=ur(o.base);I(document).subscribe(ri);let i=h(document.body,"click").pipe(He(n),v(([p,c])=>os(p,c)),pe()),a=h(window,"popstate").pipe(m(ye),pe());i.pipe(re(t)).subscribe(([p,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",p)}),O(i,a).subscribe(e);let s=e.pipe(ee("pathname"),v(p=>fn(p,{progress$:r}).pipe(de(()=>(lt(p,!0),S)))),v(ri),v(ns),pe());return O(s.pipe(re(e,(p,c)=>c)),s.pipe(v(()=>e),ee("pathname"),v(()=>e),ee("hash")),e.pipe(K((p,c)=>p.pathname===c.pathname&&p.hash===c.hash),v(()=>i),w(()=>history.back()))).subscribe(p=>{var c,l;history.state!==null||!p.hash?window.scrollTo(0,(l=(c=history.state)==null?void 0:c.y)!=null?l:0):(history.scrollRestoration="auto",pn(p.hash),history.scrollRestoration="manual")}),e.subscribe(()=>{history.scrollRestoration="manual"}),h(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),t.pipe(ee("offset"),_e(100)).subscribe(({offset:p})=>{history.replaceState(p,"")}),s}var ni=Mt(qr());function ii(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,a)=>`${i}${a}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return a=>(0,ni.default)(a).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function jt(e){return e.type===1}function dr(e){return e.type===3}function ai(e,t){let r=yn(e);return O(I(location.protocol!=="file:"),ze("search")).pipe(Ae(o=>o),v(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:B("search.suggest")}}})),r}function si(e){var l;let{selectedVersionSitemap:t,selectedVersionBaseURL:r,currentLocation:o,currentBaseURL:n}=e,i=(l=Xr(n))==null?void 0:l.pathname;if(i===void 0)return;let a=ss(o.pathname,i);if(a===void 0)return;let s=ps(t.keys());if(!t.has(s))return;let p=Xr(a,s);if(!p||!t.has(p.href))return;let c=Xr(a,r);if(c)return c.hash=o.hash,c.search=o.search,c}function Xr(e,t){try{return new URL(e,t)}catch(r){return}}function ss(e,t){if(e.startsWith(t))return e.slice(t.length)}function cs(e,t){let r=Math.min(e.length,t.length),o;for(o=0;oS)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:a,aliases:s})=>a===i||s.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),v(n=>h(document.body,"click").pipe(b(i=>!i.metaKey&&!i.ctrlKey),re(o),v(([i,a])=>{if(i.target instanceof Element){let s=i.target.closest("a");if(s&&!s.target&&n.has(s.href)){let p=s.href;return!i.target.closest(".md-version")&&n.get(p)===a?S:(i.preventDefault(),I(new URL(p)))}}return S}),v(i=>ur(i).pipe(m(a=>{var s;return(s=si({selectedVersionSitemap:a,selectedVersionBaseURL:i,currentLocation:ye(),currentBaseURL:t.base}))!=null?s:i})))))).subscribe(n=>lt(n,!0)),z([r,o]).subscribe(([n,i])=>{R(".md-header__topic").appendChild(Cn(n,i))}),e.pipe(v(()=>o)).subscribe(n=>{var a;let i=__md_get("__outdated",sessionStorage);if(i===null){i=!0;let s=((a=t.version)==null?void 0:a.default)||"latest";Array.isArray(s)||(s=[s]);e:for(let p of s)for(let c of n.aliases.concat(n.version))if(new RegExp(p,"i").test(c)){i=!1;break e}__md_set("__outdated",i,sessionStorage)}if(i)for(let s of ae("outdated"))s.hidden=!1})}function ls(e,{worker$:t}){let{searchParams:r}=ye();r.has("q")&&(Je("search",!0),e.value=r.get("q"),e.focus(),ze("search").pipe(Ae(i=>!i)).subscribe(()=>{let i=ye();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=et(e),n=O(t.pipe(Ae(jt)),h(e,"keyup"),o).pipe(m(()=>e.value),K());return z([n,o]).pipe(m(([i,a])=>({value:i,focus:a})),G(1))}function pi(e,{worker$:t}){let r=new g,o=r.pipe(Z(),ie(!0));z([t.pipe(Ae(jt)),r],(i,a)=>a).pipe(ee("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(ee("focus")).subscribe(({focus:i})=>{i&&Je("search",i)}),h(e.form,"reset").pipe(W(o)).subscribe(()=>e.focus());let n=R("header [for=__search]");return h(n,"click").subscribe(()=>e.focus()),ls(e,{worker$:t}).pipe(w(i=>r.next(i)),_(()=>r.complete()),m(i=>$({ref:e},i)),G(1))}function li(e,{worker$:t,query$:r}){let o=new g,n=on(e.parentElement).pipe(b(Boolean)),i=e.parentElement,a=R(":scope > :first-child",e),s=R(":scope > :last-child",e);ze("search").subscribe(l=>s.setAttribute("role",l?"list":"presentation")),o.pipe(re(r),Wr(t.pipe(Ae(jt)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:a.textContent=f.length?Ee("search.result.none"):Ee("search.result.placeholder");break;case 1:a.textContent=Ee("search.result.one");break;default:let u=sr(l.length);a.textContent=Ee("search.result.other",u)}});let p=o.pipe(w(()=>s.innerHTML=""),v(({items:l})=>O(I(...l.slice(0,10)),I(...l.slice(10)).pipe(Be(4),Vr(n),v(([f])=>f)))),m(Mn),pe());return p.subscribe(l=>s.appendChild(l)),p.pipe(ne(l=>{let f=fe("details",l);return typeof f=="undefined"?S:h(f,"toggle").pipe(W(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(b(dr),m(({data:l})=>l)).pipe(w(l=>o.next(l)),_(()=>o.complete()),m(l=>$({ref:e},l)))}function ms(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=ye();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function mi(e,t){let r=new g,o=r.pipe(Z(),ie(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),h(e,"click").pipe(W(o)).subscribe(n=>n.preventDefault()),ms(e,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))}function fi(e,{worker$:t,keyboard$:r}){let o=new g,n=Se("search-query"),i=O(h(n,"keydown"),h(n,"focus")).pipe(ve(se),m(()=>n.value),K());return o.pipe(He(i),m(([{suggest:s},p])=>{let c=p.split(/([\s-]+)/);if(s!=null&&s.length&&c[c.length-1]){let l=s[s.length-1];l.startsWith(c[c.length-1])&&(c[c.length-1]=l)}else c.length=0;return c})).subscribe(s=>e.innerHTML=s.join("").replace(/\s/g," ")),r.pipe(b(({mode:s})=>s==="search")).subscribe(s=>{switch(s.type){case"ArrowRight":e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText);break}}),t.pipe(b(dr),m(({data:s})=>s)).pipe(w(s=>o.next(s)),_(()=>o.complete()),m(()=>({ref:e})))}function ui(e,{index$:t,keyboard$:r}){let o=xe();try{let n=ai(o.search,t),i=Se("search-query",e),a=Se("search-result",e);h(e,"click").pipe(b(({target:p})=>p instanceof Element&&!!p.closest("a"))).subscribe(()=>Je("search",!1)),r.pipe(b(({mode:p})=>p==="search")).subscribe(p=>{let c=Ie();switch(p.type){case"Enter":if(c===i){let l=new Map;for(let f of P(":first-child [href]",a)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,d])=>d-u);f.click()}p.claim()}break;case"Escape":case"Tab":Je("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof c=="undefined")i.focus();else{let l=[i,...P(":not(details) > [href], summary, details[open] [href]",a)],f=Math.max(0,(Math.max(0,l.indexOf(c))+l.length+(p.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}p.claim();break;default:i!==Ie()&&i.focus()}}),r.pipe(b(({mode:p})=>p==="global")).subscribe(p=>{switch(p.type){case"f":case"s":case"/":i.focus(),i.select(),p.claim();break}});let s=pi(i,{worker$:n});return O(s,li(a,{worker$:n,query$:s})).pipe(Re(...ae("search-share",e).map(p=>mi(p,{query$:s})),...ae("search-suggest",e).map(p=>fi(p,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,Ye}}function di(e,{index$:t,location$:r}){return z([t,r.pipe(Q(ye()),b(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>ii(o.config)(n.searchParams.get("h"))),m(o=>{var a;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let s=i.nextNode();s;s=i.nextNode())if((a=s.parentElement)!=null&&a.offsetHeight){let p=s.textContent,c=o(p);c.length>p.length&&n.set(s,c)}for(let[s,p]of n){let{childNodes:c}=x("span",null,p);s.replaceWith(...Array.from(c))}return{ref:e,nodes:n}}))}function fs(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return z([r,t]).pipe(m(([{offset:i,height:a},{offset:{y:s}}])=>(a=a+Math.min(n,Math.max(0,s-i))-n,{height:a,locked:s>=i+n})),K((i,a)=>i.height===a.height&&i.locked===a.locked))}function Zr(e,o){var n=o,{header$:t}=n,r=so(n,["header$"]);let i=R(".md-sidebar__scrollwrap",e),{y:a}=Ve(i);return C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=s.pipe(Me(0,me));return c.pipe(re(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*a}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),c.pipe(Ae()).subscribe(()=>{for(let l of P(".md-nav__link--active[href]",e)){if(!l.clientHeight)continue;let f=l.closest(".md-sidebar__scrollwrap");if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2})}}}),ue(P("label[tabindex]",e)).pipe(ne(l=>h(l,"click").pipe(ve(se),m(()=>l),W(p)))).subscribe(l=>{let f=R(`[id="${l.htmlFor}"]`);R(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),fs(e,r).pipe(w(l=>s.next(l)),_(()=>s.complete()),m(l=>$({ref:e},l)))})}function hi(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return st(je(`${r}/releases/latest`).pipe(de(()=>S),m(o=>({version:o.tag_name})),De({})),je(r).pipe(de(()=>S),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),De({}))).pipe(m(([o,n])=>$($({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return je(r).pipe(m(o=>({repositories:o.public_repos})),De({}))}}function bi(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return st(je(`${r}/releases/permalink/latest`).pipe(de(()=>S),m(({tag_name:o})=>({version:o})),De({})),je(r).pipe(de(()=>S),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),De({}))).pipe(m(([o,n])=>$($({},o),n)))}function vi(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return hi(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return bi(r,o)}return S}var us;function ds(e){return us||(us=C(()=>{let t=__md_get("__source",sessionStorage);if(t)return I(t);if(ae("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return S}return vi(e.href).pipe(w(o=>__md_set("__source",o,sessionStorage)))}).pipe(de(()=>S),b(t=>Object.keys(t).length>0),m(t=>({facts:t})),G(1)))}function gi(e){let t=R(":scope > :last-child",e);return C(()=>{let r=new g;return r.subscribe(({facts:o})=>{t.appendChild(_n(o)),t.classList.add("md-source__repository--active")}),ds(e).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function hs(e,{viewport$:t,header$:r}){return ge(document.body).pipe(v(()=>mr(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),ee("hidden"))}function yi(e,t){return C(()=>{let r=new g;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(B("navigation.tabs.sticky")?I({hidden:!1}):hs(e,t)).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function bs(e,{viewport$:t,header$:r}){let o=new Map,n=P(".md-nav__link",e);for(let s of n){let p=decodeURIComponent(s.hash.substring(1)),c=fe(`[id="${p}"]`);typeof c!="undefined"&&o.set(s,c)}let i=r.pipe(ee("height"),m(({height:s})=>{let p=Se("main"),c=R(":scope > :first-child",p);return s+.8*(c.offsetTop-p.offsetTop)}),pe());return ge(document.body).pipe(ee("height"),v(s=>C(()=>{let p=[];return I([...o].reduce((c,[l,f])=>{for(;p.length&&o.get(p[p.length-1]).tagName>=f.tagName;)p.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let d=f.offsetParent;for(;d;d=d.offsetParent)u+=d.offsetTop;return c.set([...p=[...p,l]].reverse(),u)},new Map))}).pipe(m(p=>new Map([...p].sort(([,c],[,l])=>c-l))),He(i),v(([p,c])=>t.pipe(Fr(([l,f],{offset:{y:u},size:d})=>{let y=u+d.height>=Math.floor(s.height);for(;f.length;){let[,L]=f[0];if(L-c=u&&!y)f=[l.pop(),...f];else break}return[l,f]},[[],[...p]]),K((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([s,p])=>({prev:s.map(([c])=>c),next:p.map(([c])=>c)})),Q({prev:[],next:[]}),Be(2,1),m(([s,p])=>s.prev.length{let i=new g,a=i.pipe(Z(),ie(!0));if(i.subscribe(({prev:s,next:p})=>{for(let[c]of p)c.classList.remove("md-nav__link--passed"),c.classList.remove("md-nav__link--active");for(let[c,[l]]of s.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",c===s.length-1)}),B("toc.follow")){let s=O(t.pipe(_e(1),m(()=>{})),t.pipe(_e(250),m(()=>"smooth")));i.pipe(b(({prev:p})=>p.length>0),He(o.pipe(ve(se))),re(s)).subscribe(([[{prev:p}],c])=>{let[l]=p[p.length-1];if(l.offsetHeight){let f=cr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2,behavior:c})}}})}return B("navigation.tracking")&&t.pipe(W(a),ee("offset"),_e(250),Ce(1),W(n.pipe(Ce(1))),ct({delay:250}),re(i)).subscribe(([,{prev:s}])=>{let p=ye(),c=s[s.length-1];if(c&&c.length){let[l]=c,{hash:f}=new URL(l.href);p.hash!==f&&(p.hash=f,history.replaceState({},"",`${p}`))}else p.hash="",history.replaceState({},"",`${p}`)}),bs(e,{viewport$:t,header$:r}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function vs(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:a}})=>a),Be(2,1),m(([a,s])=>a>s&&s>0),K()),i=r.pipe(m(({active:a})=>a));return z([i,n]).pipe(m(([a,s])=>!(a&&s)),K(),W(o.pipe(Ce(1))),ie(!0),ct({delay:250}),m(a=>({hidden:a})))}function Ei(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({hidden:s}){e.hidden=s,s?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(W(a),ee("height")).subscribe(({height:s})=>{e.style.top=`${s+16}px`}),h(e,"click").subscribe(s=>{s.preventDefault(),window.scrollTo({top:0})}),vs(e,{viewport$:t,main$:o,target$:n}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))}function wi({document$:e,viewport$:t}){e.pipe(v(()=>P(".md-ellipsis")),ne(r=>tt(r).pipe(W(e.pipe(Ce(1))),b(o=>o),m(()=>r),Te(1))),b(r=>r.offsetWidth{let o=r.innerText,n=r.closest("a")||r;return n.title=o,B("content.tooltips")?mt(n,{viewport$:t}).pipe(W(e.pipe(Ce(1))),_(()=>n.removeAttribute("title"))):S})).subscribe(),B("content.tooltips")&&e.pipe(v(()=>P(".md-status")),ne(r=>mt(r,{viewport$:t}))).subscribe()}function Ti({document$:e,tablet$:t}){e.pipe(v(()=>P(".md-toggle--indeterminate")),w(r=>{r.indeterminate=!0,r.checked=!1}),ne(r=>h(r,"change").pipe(Dr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),re(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function gs(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Si({document$:e}){e.pipe(v(()=>P("[data-md-scrollfix]")),w(t=>t.removeAttribute("data-md-scrollfix")),b(gs),ne(t=>h(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function Oi({viewport$:e,tablet$:t}){z([ze("search"),t]).pipe(m(([r,o])=>r&&!o),v(r=>I(r).pipe(Ge(r?400:100))),re(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function ys(){return location.protocol==="file:"?Tt(`${new URL("search/search_index.js",eo.base)}`).pipe(m(()=>__index),G(1)):je(new URL("search/search_index.json",eo.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var ot=Go(),Ut=sn(),Lt=ln(Ut),to=an(),Oe=gn(),hr=Pt("(min-width: 960px)"),Mi=Pt("(min-width: 1220px)"),_i=mn(),eo=xe(),Ai=document.forms.namedItem("search")?ys():Ye,ro=new g;Zn({alert$:ro});var oo=new g;B("navigation.instant")&&oi({location$:Ut,viewport$:Oe,progress$:oo}).subscribe(ot);var Li;((Li=eo.version)==null?void 0:Li.provider)==="mike"&&ci({document$:ot});O(Ut,Lt).pipe(Ge(125)).subscribe(()=>{Je("drawer",!1),Je("search",!1)});to.pipe(b(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=fe("link[rel=prev]");typeof t!="undefined"&<(t);break;case"n":case".":let r=fe("link[rel=next]");typeof r!="undefined"&<(r);break;case"Enter":let o=Ie();o instanceof HTMLLabelElement&&o.click()}});wi({viewport$:Oe,document$:ot});Ti({document$:ot,tablet$:hr});Si({document$:ot});Oi({viewport$:Oe,tablet$:hr});var rt=Kn(Se("header"),{viewport$:Oe}),Ft=ot.pipe(m(()=>Se("main")),v(e=>Gn(e,{viewport$:Oe,header$:rt})),G(1)),xs=O(...ae("consent").map(e=>En(e,{target$:Lt})),...ae("dialog").map(e=>qn(e,{alert$:ro})),...ae("palette").map(e=>Jn(e)),...ae("progress").map(e=>Xn(e,{progress$:oo})),...ae("search").map(e=>ui(e,{index$:Ai,keyboard$:to})),...ae("source").map(e=>gi(e))),Es=C(()=>O(...ae("announce").map(e=>xn(e)),...ae("content").map(e=>zn(e,{viewport$:Oe,target$:Lt,print$:_i})),...ae("content").map(e=>B("search.highlight")?di(e,{index$:Ai,location$:Ut}):S),...ae("header").map(e=>Yn(e,{viewport$:Oe,header$:rt,main$:Ft})),...ae("header-title").map(e=>Bn(e,{viewport$:Oe,header$:rt})),...ae("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?Nr(Mi,()=>Zr(e,{viewport$:Oe,header$:rt,main$:Ft})):Nr(hr,()=>Zr(e,{viewport$:Oe,header$:rt,main$:Ft}))),...ae("tabs").map(e=>yi(e,{viewport$:Oe,header$:rt})),...ae("toc").map(e=>xi(e,{viewport$:Oe,header$:rt,main$:Ft,target$:Lt})),...ae("top").map(e=>Ei(e,{viewport$:Oe,header$:rt,main$:Ft,target$:Lt})))),Ci=ot.pipe(v(()=>Es),Re(xs),G(1));Ci.subscribe();window.document$=ot;window.location$=Ut;window.target$=Lt;window.keyboard$=to;window.viewport$=Oe;window.tablet$=hr;window.screen$=Mi;window.print$=_i;window.alert$=ro;window.progress$=oo;window.component$=Ci;})(); +//# sourceMappingURL=bundle.88dd0f4e.min.js.map + diff --git a/assets/javascripts/bundle.88dd0f4e.min.js.map b/assets/javascripts/bundle.88dd0f4e.min.js.map new file mode 100644 index 00000000..dab2a875 --- /dev/null +++ b/assets/javascripts/bundle.88dd0f4e.min.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/escape-html/index.js", "node_modules/clipboard/dist/clipboard.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/tslib/tslib.es6.mjs", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/BehaviorSubject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/QueueAction.ts", "node_modules/rxjs/src/internal/scheduler/QueueScheduler.ts", "node_modules/rxjs/src/internal/scheduler/queue.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/EmptyError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/debounce.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/throwIfEmpty.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/first.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/templates/assets/javascripts/browser/document/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/browser/element/focus/index.ts", "src/templates/assets/javascripts/browser/element/hover/index.ts", "src/templates/assets/javascripts/utilities/h/index.ts", "src/templates/assets/javascripts/utilities/round/index.ts", "src/templates/assets/javascripts/browser/script/index.ts", "src/templates/assets/javascripts/browser/element/size/_/index.ts", "src/templates/assets/javascripts/browser/element/size/content/index.ts", "src/templates/assets/javascripts/browser/element/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/content/index.ts", "src/templates/assets/javascripts/browser/element/visibility/index.ts", "src/templates/assets/javascripts/browser/toggle/index.ts", "src/templates/assets/javascripts/browser/keyboard/index.ts", "src/templates/assets/javascripts/browser/location/_/index.ts", "src/templates/assets/javascripts/browser/location/hash/index.ts", "src/templates/assets/javascripts/browser/media/index.ts", "src/templates/assets/javascripts/browser/request/index.ts", "src/templates/assets/javascripts/browser/viewport/offset/index.ts", "src/templates/assets/javascripts/browser/viewport/size/index.ts", "src/templates/assets/javascripts/browser/viewport/_/index.ts", "src/templates/assets/javascripts/browser/viewport/at/index.ts", "src/templates/assets/javascripts/browser/worker/index.ts", "src/templates/assets/javascripts/_/index.ts", "src/templates/assets/javascripts/components/_/index.ts", "src/templates/assets/javascripts/components/announce/index.ts", "src/templates/assets/javascripts/components/consent/index.ts", "src/templates/assets/javascripts/templates/tooltip/index.tsx", "src/templates/assets/javascripts/templates/annotation/index.tsx", "src/templates/assets/javascripts/templates/clipboard/index.tsx", "src/templates/assets/javascripts/templates/search/index.tsx", "src/templates/assets/javascripts/templates/source/index.tsx", "src/templates/assets/javascripts/templates/tabbed/index.tsx", "src/templates/assets/javascripts/templates/table/index.tsx", "src/templates/assets/javascripts/templates/version/index.tsx", "src/templates/assets/javascripts/components/tooltip2/index.ts", "src/templates/assets/javascripts/components/content/annotation/_/index.ts", "src/templates/assets/javascripts/components/content/annotation/list/index.ts", "src/templates/assets/javascripts/components/content/annotation/block/index.ts", "src/templates/assets/javascripts/components/content/code/_/index.ts", "src/templates/assets/javascripts/components/content/details/index.ts", "src/templates/assets/javascripts/components/content/mermaid/index.css", "src/templates/assets/javascripts/components/content/mermaid/index.ts", "src/templates/assets/javascripts/components/content/table/index.ts", "src/templates/assets/javascripts/components/content/tabs/index.ts", "src/templates/assets/javascripts/components/content/_/index.ts", "src/templates/assets/javascripts/components/dialog/index.ts", "src/templates/assets/javascripts/components/tooltip/index.ts", "src/templates/assets/javascripts/components/header/_/index.ts", "src/templates/assets/javascripts/components/header/title/index.ts", "src/templates/assets/javascripts/components/main/index.ts", "src/templates/assets/javascripts/components/palette/index.ts", "src/templates/assets/javascripts/components/progress/index.ts", "src/templates/assets/javascripts/integrations/clipboard/index.ts", "src/templates/assets/javascripts/integrations/sitemap/index.ts", "src/templates/assets/javascripts/integrations/instant/index.ts", "src/templates/assets/javascripts/integrations/search/highlighter/index.ts", "src/templates/assets/javascripts/integrations/search/worker/message/index.ts", "src/templates/assets/javascripts/integrations/search/worker/_/index.ts", "src/templates/assets/javascripts/integrations/version/findurl/index.ts", "src/templates/assets/javascripts/integrations/version/index.ts", "src/templates/assets/javascripts/components/search/query/index.ts", "src/templates/assets/javascripts/components/search/result/index.ts", "src/templates/assets/javascripts/components/search/share/index.ts", "src/templates/assets/javascripts/components/search/suggest/index.ts", "src/templates/assets/javascripts/components/search/_/index.ts", "src/templates/assets/javascripts/components/search/highlight/index.ts", "src/templates/assets/javascripts/components/sidebar/index.ts", "src/templates/assets/javascripts/components/source/facts/github/index.ts", "src/templates/assets/javascripts/components/source/facts/gitlab/index.ts", "src/templates/assets/javascripts/components/source/facts/_/index.ts", "src/templates/assets/javascripts/components/source/_/index.ts", "src/templates/assets/javascripts/components/tabs/index.ts", "src/templates/assets/javascripts/components/toc/index.ts", "src/templates/assets/javascripts/components/top/index.ts", "src/templates/assets/javascripts/patches/ellipsis/index.ts", "src/templates/assets/javascripts/patches/indeterminate/index.ts", "src/templates/assets/javascripts/patches/scrollfix/index.ts", "src/templates/assets/javascripts/patches/scrolllock/index.ts", "src/templates/assets/javascripts/polyfills/index.ts"], + "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getActiveElement,\n getOptionalElement,\n requestJSON,\n setLocation,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchScript,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountAnnounce,\n mountBackToTop,\n mountConsent,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountProgress,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n setupClipboardJS,\n setupInstantNavigation,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchEllipsis,\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Functions - @todo refactor\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch search index\n *\n * @returns Search index observable\n */\nfunction fetchSearchIndex(): Observable {\n if (location.protocol === \"file:\") {\n return watchScript(\n `${new URL(\"search/search_index.js\", config.base)}`\n )\n .pipe(\n // @ts-ignore - @todo fix typings\n map(() => __index),\n shareReplay(1)\n )\n } else {\n return requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget(location$)\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 960px)\")\nconst screen$ = watchMedia(\"(min-width: 1220px)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? fetchSearchIndex()\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up progress indicator */\nconst progress$ = new Subject()\n\n/* Set up instant navigation, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantNavigation({ location$, viewport$, progress$ })\n .subscribe(document$)\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"link[rel=prev]\")\n if (typeof prev !== \"undefined\")\n setLocation(prev)\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"link[rel=next]\")\n if (typeof next !== \"undefined\")\n setLocation(next)\n break\n\n /* Expand navigation, see https://bit.ly/3ZjG5io */\n case \"Enter\":\n const active = getActiveElement()\n if (active instanceof HTMLLabelElement)\n active.click()\n }\n })\n\n/* Set up patches */\npatchEllipsis({ viewport$, document$ })\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Consent */\n ...getComponentElements(\"consent\")\n .map(el => mountConsent(el, { target$ })),\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Progress bar */\n ...getComponentElements(\"progress\")\n .map(el => mountProgress(el, { progress$ })),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Announcement bar */\n ...getComponentElements(\"announce\")\n .map(el => mountAnnounce(el)),\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { viewport$, target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, {\n viewport$, header$, main$, target$\n })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.progress$ = progress$ /* Progress indicator subject */\nwindow.component$ = component$ /* Component observable */\n", "/******************************************************************************\nCopyright (c) Microsoft Corporation.\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n***************************************************************************** */\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\n\nvar extendStatics = function(d, b) {\n extendStatics = Object.setPrototypeOf ||\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\n return extendStatics(d, b);\n};\n\nexport function __extends(d, b) {\n if (typeof b !== \"function\" && b !== null)\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\n extendStatics(d, b);\n function __() { this.constructor = d; }\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\n}\n\nexport var __assign = function() {\n __assign = Object.assign || function __assign(t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\n }\n return t;\n }\n return __assign.apply(this, arguments);\n}\n\nexport function __rest(s, e) {\n var t = {};\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\n t[p] = s[p];\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\n t[p[i]] = s[p[i]];\n }\n return t;\n}\n\nexport function __decorate(decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n}\n\nexport function __param(paramIndex, decorator) {\n return function (target, key) { decorator(target, key, paramIndex); }\n}\n\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\n var _, done = false;\n for (var i = decorators.length - 1; i >= 0; i--) {\n var context = {};\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\n if (kind === \"accessor\") {\n if (result === void 0) continue;\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\n if (_ = accept(result.get)) descriptor.get = _;\n if (_ = accept(result.set)) descriptor.set = _;\n if (_ = accept(result.init)) initializers.unshift(_);\n }\n else if (_ = accept(result)) {\n if (kind === \"field\") initializers.unshift(_);\n else descriptor[key] = _;\n }\n }\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\n done = true;\n};\n\nexport function __runInitializers(thisArg, initializers, value) {\n var useValue = arguments.length > 2;\n for (var i = 0; i < initializers.length; i++) {\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\n }\n return useValue ? value : void 0;\n};\n\nexport function __propKey(x) {\n return typeof x === \"symbol\" ? x : \"\".concat(x);\n};\n\nexport function __setFunctionName(f, name, prefix) {\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\n};\n\nexport function __metadata(metadataKey, metadataValue) {\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\n}\n\nexport function __awaiter(thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n}\n\nexport function __generator(thisArg, body) {\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\n function verb(n) { return function (v) { return step([n, v]); }; }\n function step(op) {\n if (f) throw new TypeError(\"Generator is already executing.\");\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\n if (y = 0, t) op = [op[0] & 2, t.value];\n switch (op[0]) {\n case 0: case 1: t = op; break;\n case 4: _.label++; return { value: op[1], done: false };\n case 5: _.label++; y = op[1]; op = [0]; continue;\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\n default:\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\n if (t[2]) _.ops.pop();\n _.trys.pop(); continue;\n }\n op = body.call(thisArg, _);\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\n }\n}\n\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n});\n\nexport function __exportStar(m, o) {\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\n}\n\nexport function __values(o) {\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\n if (m) return m.call(o);\n if (o && typeof o.length === \"number\") return {\n next: function () {\n if (o && i >= o.length) o = void 0;\n return { value: o && o[i++], done: !o };\n }\n };\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\n}\n\nexport function __read(o, n) {\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\n if (!m) return o;\n var i = m.call(o), r, ar = [], e;\n try {\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\n }\n catch (error) { e = { error: error }; }\n finally {\n try {\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\n }\n finally { if (e) throw e.error; }\n }\n return ar;\n}\n\n/** @deprecated */\nexport function __spread() {\n for (var ar = [], i = 0; i < arguments.length; i++)\n ar = ar.concat(__read(arguments[i]));\n return ar;\n}\n\n/** @deprecated */\nexport function __spreadArrays() {\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\n r[k] = a[j];\n return r;\n}\n\nexport function __spreadArray(to, from, pack) {\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\n if (ar || !(i in from)) {\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\n ar[i] = from[i];\n }\n }\n return to.concat(ar || Array.prototype.slice.call(from));\n}\n\nexport function __await(v) {\n return this instanceof __await ? (this.v = v, this) : new __await(v);\n}\n\nexport function __asyncGenerator(thisArg, _arguments, generator) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\n function fulfill(value) { resume(\"next\", value); }\n function reject(value) { resume(\"throw\", value); }\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\n}\n\nexport function __asyncDelegator(o) {\n var i, p;\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\n}\n\nexport function __asyncValues(o) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var m = o[Symbol.asyncIterator], i;\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\n}\n\nexport function __makeTemplateObject(cooked, raw) {\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\n return cooked;\n};\n\nvar __setModuleDefault = Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n};\n\nexport function __importStar(mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\n __setModuleDefault(result, mod);\n return result;\n}\n\nexport function __importDefault(mod) {\n return (mod && mod.__esModule) ? mod : { default: mod };\n}\n\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\n}\n\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\n}\n\nexport function __classPrivateFieldIn(state, receiver) {\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\n}\n\nexport function __addDisposableResource(env, value, async) {\n if (value !== null && value !== void 0) {\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\n var dispose, inner;\n if (async) {\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\n dispose = value[Symbol.asyncDispose];\n }\n if (dispose === void 0) {\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\n dispose = value[Symbol.dispose];\n if (async) inner = dispose;\n }\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\n env.stack.push({ value: value, dispose: dispose, async: async });\n }\n else if (async) {\n env.stack.push({ async: true });\n }\n return value;\n}\n\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\n var e = new Error(message);\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\n};\n\nexport function __disposeResources(env) {\n function fail(e) {\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\n env.hasError = true;\n }\n var r, s = 0;\n function next() {\n while (r = env.stack.pop()) {\n try {\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\n if (r.dispose) {\n var result = r.dispose.call(r.value);\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\n }\n else s |= 1;\n }\n catch (e) {\n fail(e);\n }\n }\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\n if (env.hasError) throw env.error;\n }\n return next();\n}\n\nexport default {\n __extends,\n __assign,\n __rest,\n __decorate,\n __param,\n __metadata,\n __awaiter,\n __generator,\n __createBinding,\n __exportStar,\n __values,\n __read,\n __spread,\n __spreadArrays,\n __spreadArray,\n __await,\n __asyncGenerator,\n __asyncDelegator,\n __asyncValues,\n __makeTemplateObject,\n __importStar,\n __importDefault,\n __classPrivateFieldGet,\n __classPrivateFieldSet,\n __classPrivateFieldIn,\n __addDisposableResource,\n __disposeResources,\n};\n", "/**\n * Returns true if the object is a function.\n * @param value The value to check\n */\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "/**\n * Used to create Error subclasses until the community moves away from ES5.\n *\n * This is because compiling from TypeScript down to ES5 has issues with subclassing Errors\n * as well as other built-in types: https://github.com/Microsoft/TypeScript/issues/12123\n *\n * @param createImpl A factory function to create the actual constructor implementation. The returned\n * function should be a named function that calls `_super` internally.\n */\nexport function createErrorClass(createImpl: (_super: any) => any): T {\n const _super = (instance: any) => {\n Error.call(instance);\n instance.stack = new Error().stack;\n };\n\n const ctorFunc = createImpl(_super);\n ctorFunc.prototype = Object.create(Error.prototype);\n ctorFunc.prototype.constructor = ctorFunc;\n return ctorFunc;\n}\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface UnsubscriptionError extends Error {\n readonly errors: any[];\n}\n\nexport interface UnsubscriptionErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (errors: any[]): UnsubscriptionError;\n}\n\n/**\n * An error thrown when one or more errors have occurred during the\n * `unsubscribe` of a {@link Subscription}.\n */\nexport const UnsubscriptionError: UnsubscriptionErrorCtor = createErrorClass(\n (_super) =>\n function UnsubscriptionErrorImpl(this: any, errors: (Error | string)[]) {\n _super(this);\n this.message = errors\n ? `${errors.length} errors occurred during unsubscription:\n${errors.map((err, i) => `${i + 1}) ${err.toString()}`).join('\\n ')}`\n : '';\n this.name = 'UnsubscriptionError';\n this.errors = errors;\n }\n);\n", "/**\n * Removes an item from an array, mutating it.\n * @param arr The array to remove the item from\n * @param item The item to remove\n */\nexport function arrRemove(arr: T[] | undefined | null, item: T) {\n if (arr) {\n const index = arr.indexOf(item);\n 0 <= index && arr.splice(index, 1);\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { UnsubscriptionError } from './util/UnsubscriptionError';\nimport { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';\nimport { arrRemove } from './util/arrRemove';\n\n/**\n * Represents a disposable resource, such as the execution of an Observable. A\n * Subscription has one important method, `unsubscribe`, that takes no argument\n * and just disposes the resource held by the subscription.\n *\n * Additionally, subscriptions may be grouped together through the `add()`\n * method, which will attach a child Subscription to the current Subscription.\n * When a Subscription is unsubscribed, all its children (and its grandchildren)\n * will be unsubscribed as well.\n *\n * @class Subscription\n */\nexport class Subscription implements SubscriptionLike {\n /** @nocollapse */\n public static EMPTY = (() => {\n const empty = new Subscription();\n empty.closed = true;\n return empty;\n })();\n\n /**\n * A flag to indicate whether this Subscription has already been unsubscribed.\n */\n public closed = false;\n\n private _parentage: Subscription[] | Subscription | null = null;\n\n /**\n * The list of registered finalizers to execute upon unsubscription. Adding and removing from this\n * list occurs in the {@link #add} and {@link #remove} methods.\n */\n private _finalizers: Exclude[] | null = null;\n\n /**\n * @param initialTeardown A function executed first as part of the finalization\n * process that is kicked off when {@link #unsubscribe} is called.\n */\n constructor(private initialTeardown?: () => void) {}\n\n /**\n * Disposes the resources held by the subscription. May, for instance, cancel\n * an ongoing Observable execution or cancel any other type of work that\n * started when the Subscription was created.\n * @return {void}\n */\n unsubscribe(): void {\n let errors: any[] | undefined;\n\n if (!this.closed) {\n this.closed = true;\n\n // Remove this from it's parents.\n const { _parentage } = this;\n if (_parentage) {\n this._parentage = null;\n if (Array.isArray(_parentage)) {\n for (const parent of _parentage) {\n parent.remove(this);\n }\n } else {\n _parentage.remove(this);\n }\n }\n\n const { initialTeardown: initialFinalizer } = this;\n if (isFunction(initialFinalizer)) {\n try {\n initialFinalizer();\n } catch (e) {\n errors = e instanceof UnsubscriptionError ? e.errors : [e];\n }\n }\n\n const { _finalizers } = this;\n if (_finalizers) {\n this._finalizers = null;\n for (const finalizer of _finalizers) {\n try {\n execFinalizer(finalizer);\n } catch (err) {\n errors = errors ?? [];\n if (err instanceof UnsubscriptionError) {\n errors = [...errors, ...err.errors];\n } else {\n errors.push(err);\n }\n }\n }\n }\n\n if (errors) {\n throw new UnsubscriptionError(errors);\n }\n }\n }\n\n /**\n * Adds a finalizer to this subscription, so that finalization will be unsubscribed/called\n * when this subscription is unsubscribed. If this subscription is already {@link #closed},\n * because it has already been unsubscribed, then whatever finalizer is passed to it\n * will automatically be executed (unless the finalizer itself is also a closed subscription).\n *\n * Closed Subscriptions cannot be added as finalizers to any subscription. Adding a closed\n * subscription to a any subscription will result in no operation. (A noop).\n *\n * Adding a subscription to itself, or adding `null` or `undefined` will not perform any\n * operation at all. (A noop).\n *\n * `Subscription` instances that are added to this instance will automatically remove themselves\n * if they are unsubscribed. Functions and {@link Unsubscribable} objects that you wish to remove\n * will need to be removed manually with {@link #remove}\n *\n * @param teardown The finalization logic to add to this subscription.\n */\n add(teardown: TeardownLogic): void {\n // Only add the finalizer if it's not undefined\n // and don't add a subscription to itself.\n if (teardown && teardown !== this) {\n if (this.closed) {\n // If this subscription is already closed,\n // execute whatever finalizer is handed to it automatically.\n execFinalizer(teardown);\n } else {\n if (teardown instanceof Subscription) {\n // We don't add closed subscriptions, and we don't add the same subscription\n // twice. Subscription unsubscribe is idempotent.\n if (teardown.closed || teardown._hasParent(this)) {\n return;\n }\n teardown._addParent(this);\n }\n (this._finalizers = this._finalizers ?? []).push(teardown);\n }\n }\n }\n\n /**\n * Checks to see if a this subscription already has a particular parent.\n * This will signal that this subscription has already been added to the parent in question.\n * @param parent the parent to check for\n */\n private _hasParent(parent: Subscription) {\n const { _parentage } = this;\n return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));\n }\n\n /**\n * Adds a parent to this subscription so it can be removed from the parent if it\n * unsubscribes on it's own.\n *\n * NOTE: THIS ASSUMES THAT {@link _hasParent} HAS ALREADY BEEN CHECKED.\n * @param parent The parent subscription to add\n */\n private _addParent(parent: Subscription) {\n const { _parentage } = this;\n this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;\n }\n\n /**\n * Called on a child when it is removed via {@link #remove}.\n * @param parent The parent to remove\n */\n private _removeParent(parent: Subscription) {\n const { _parentage } = this;\n if (_parentage === parent) {\n this._parentage = null;\n } else if (Array.isArray(_parentage)) {\n arrRemove(_parentage, parent);\n }\n }\n\n /**\n * Removes a finalizer from this subscription that was previously added with the {@link #add} method.\n *\n * Note that `Subscription` instances, when unsubscribed, will automatically remove themselves\n * from every other `Subscription` they have been added to. This means that using the `remove` method\n * is not a common thing and should be used thoughtfully.\n *\n * If you add the same finalizer instance of a function or an unsubscribable object to a `Subscription` instance\n * more than once, you will need to call `remove` the same number of times to remove all instances.\n *\n * All finalizer instances are removed to free up memory upon unsubscription.\n *\n * @param teardown The finalizer to remove from this subscription\n */\n remove(teardown: Exclude): void {\n const { _finalizers } = this;\n _finalizers && arrRemove(_finalizers, teardown);\n\n if (teardown instanceof Subscription) {\n teardown._removeParent(this);\n }\n }\n}\n\nexport const EMPTY_SUBSCRIPTION = Subscription.EMPTY;\n\nexport function isSubscription(value: any): value is Subscription {\n return (\n value instanceof Subscription ||\n (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))\n );\n}\n\nfunction execFinalizer(finalizer: Unsubscribable | (() => void)) {\n if (isFunction(finalizer)) {\n finalizer();\n } else {\n finalizer.unsubscribe();\n }\n}\n", "import { Subscriber } from './Subscriber';\nimport { ObservableNotification } from './types';\n\n/**\n * The {@link GlobalConfig} object for RxJS. It is used to configure things\n * like how to react on unhandled errors.\n */\nexport const config: GlobalConfig = {\n onUnhandledError: null,\n onStoppedNotification: null,\n Promise: undefined,\n useDeprecatedSynchronousErrorHandling: false,\n useDeprecatedNextContext: false,\n};\n\n/**\n * The global configuration object for RxJS, used to configure things\n * like how to react on unhandled errors. Accessible via {@link config}\n * object.\n */\nexport interface GlobalConfig {\n /**\n * A registration point for unhandled errors from RxJS. These are errors that\n * cannot were not handled by consuming code in the usual subscription path. For\n * example, if you have this configured, and you subscribe to an observable without\n * providing an error handler, errors from that subscription will end up here. This\n * will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onUnhandledError: ((err: any) => void) | null;\n\n /**\n * A registration point for notifications that cannot be sent to subscribers because they\n * have completed, errored or have been explicitly unsubscribed. By default, next, complete\n * and error notifications sent to stopped subscribers are noops. However, sometimes callers\n * might want a different behavior. For example, with sources that attempt to report errors\n * to stopped subscribers, a caller can configure RxJS to throw an unhandled error instead.\n * This will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onStoppedNotification: ((notification: ObservableNotification, subscriber: Subscriber) => void) | null;\n\n /**\n * The promise constructor used by default for {@link Observable#toPromise toPromise} and {@link Observable#forEach forEach}\n * methods.\n *\n * @deprecated As of version 8, RxJS will no longer support this sort of injection of a\n * Promise constructor. If you need a Promise implementation other than native promises,\n * please polyfill/patch Promise as you see appropriate. Will be removed in v8.\n */\n Promise?: PromiseConstructorLike;\n\n /**\n * If true, turns on synchronous error rethrowing, which is a deprecated behavior\n * in v6 and higher. This behavior enables bad patterns like wrapping a subscribe\n * call in a try/catch block. It also enables producer interference, a nasty bug\n * where a multicast can be broken for all observers by a downstream consumer with\n * an unhandled error. DO NOT USE THIS FLAG UNLESS IT'S NEEDED TO BUY TIME\n * FOR MIGRATION REASONS.\n *\n * @deprecated As of version 8, RxJS will no longer support synchronous throwing\n * of unhandled errors. All errors will be thrown on a separate call stack to prevent bad\n * behaviors described above. Will be removed in v8.\n */\n useDeprecatedSynchronousErrorHandling: boolean;\n\n /**\n * If true, enables an as-of-yet undocumented feature from v5: The ability to access\n * `unsubscribe()` via `this` context in `next` functions created in observers passed\n * to `subscribe`.\n *\n * This is being removed because the performance was severely problematic, and it could also cause\n * issues when types other than POJOs are passed to subscribe as subscribers, as they will likely have\n * their `this` context overwritten.\n *\n * @deprecated As of version 8, RxJS will no longer support altering the\n * context of next functions provided as part of an observer to Subscribe. Instead,\n * you will have access to a subscription or a signal or token that will allow you to do things like\n * unsubscribe and test closed status. Will be removed in v8.\n */\n useDeprecatedNextContext: boolean;\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetTimeoutFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearTimeoutFunction = (handle: TimerHandle) => void;\n\ninterface TimeoutProvider {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n delegate:\n | {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n }\n | undefined;\n}\n\nexport const timeoutProvider: TimeoutProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setTimeout(handler: () => void, timeout?: number, ...args) {\n const { delegate } = timeoutProvider;\n if (delegate?.setTimeout) {\n return delegate.setTimeout(handler, timeout, ...args);\n }\n return setTimeout(handler, timeout, ...args);\n },\n clearTimeout(handle) {\n const { delegate } = timeoutProvider;\n return (delegate?.clearTimeout || clearTimeout)(handle as any);\n },\n delegate: undefined,\n};\n", "import { config } from '../config';\nimport { timeoutProvider } from '../scheduler/timeoutProvider';\n\n/**\n * Handles an error on another job either with the user-configured {@link onUnhandledError},\n * or by throwing it on that new job so it can be picked up by `window.onerror`, `process.on('error')`, etc.\n *\n * This should be called whenever there is an error that is out-of-band with the subscription\n * or when an error hits a terminal boundary of the subscription and no error handler was provided.\n *\n * @param err the error to report\n */\nexport function reportUnhandledError(err: any) {\n timeoutProvider.setTimeout(() => {\n const { onUnhandledError } = config;\n if (onUnhandledError) {\n // Execute the user-configured error handler.\n onUnhandledError(err);\n } else {\n // Throw so it is picked up by the runtime's uncaught error mechanism.\n throw err;\n }\n });\n}\n", "/* tslint:disable:no-empty */\nexport function noop() { }\n", "import { CompleteNotification, NextNotification, ErrorNotification } from './types';\n\n/**\n * A completion object optimized for memory use and created to be the\n * same \"shape\" as other notifications in v8.\n * @internal\n */\nexport const COMPLETE_NOTIFICATION = (() => createNotification('C', undefined, undefined) as CompleteNotification)();\n\n/**\n * Internal use only. Creates an optimized error notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function errorNotification(error: any): ErrorNotification {\n return createNotification('E', undefined, error) as any;\n}\n\n/**\n * Internal use only. Creates an optimized next notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function nextNotification(value: T) {\n return createNotification('N', value, undefined) as NextNotification;\n}\n\n/**\n * Ensures that all notifications created internally have the same \"shape\" in v8.\n *\n * TODO: This is only exported to support a crazy legacy test in `groupBy`.\n * @internal\n */\nexport function createNotification(kind: 'N' | 'E' | 'C', value: any, error: any) {\n return {\n kind,\n value,\n error,\n };\n}\n", "import { config } from '../config';\n\nlet context: { errorThrown: boolean; error: any } | null = null;\n\n/**\n * Handles dealing with errors for super-gross mode. Creates a context, in which\n * any synchronously thrown errors will be passed to {@link captureError}. Which\n * will record the error such that it will be rethrown after the call back is complete.\n * TODO: Remove in v8\n * @param cb An immediately executed function.\n */\nexport function errorContext(cb: () => void) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n const isRoot = !context;\n if (isRoot) {\n context = { errorThrown: false, error: null };\n }\n cb();\n if (isRoot) {\n const { errorThrown, error } = context!;\n context = null;\n if (errorThrown) {\n throw error;\n }\n }\n } else {\n // This is the general non-deprecated path for everyone that\n // isn't crazy enough to use super-gross mode (useDeprecatedSynchronousErrorHandling)\n cb();\n }\n}\n\n/**\n * Captures errors only in super-gross mode.\n * @param err the error to capture\n */\nexport function captureError(err: any) {\n if (config.useDeprecatedSynchronousErrorHandling && context) {\n context.errorThrown = true;\n context.error = err;\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { Observer, ObservableNotification } from './types';\nimport { isSubscription, Subscription } from './Subscription';\nimport { config } from './config';\nimport { reportUnhandledError } from './util/reportUnhandledError';\nimport { noop } from './util/noop';\nimport { nextNotification, errorNotification, COMPLETE_NOTIFICATION } from './NotificationFactories';\nimport { timeoutProvider } from './scheduler/timeoutProvider';\nimport { captureError } from './util/errorContext';\n\n/**\n * Implements the {@link Observer} interface and extends the\n * {@link Subscription} class. While the {@link Observer} is the public API for\n * consuming the values of an {@link Observable}, all Observers get converted to\n * a Subscriber, in order to provide Subscription-like capabilities such as\n * `unsubscribe`. Subscriber is a common type in RxJS, and crucial for\n * implementing operators, but it is rarely used as a public API.\n *\n * @class Subscriber\n */\nexport class Subscriber extends Subscription implements Observer {\n /**\n * A static factory for a Subscriber, given a (potentially partial) definition\n * of an Observer.\n * @param next The `next` callback of an Observer.\n * @param error The `error` callback of an\n * Observer.\n * @param complete The `complete` callback of an\n * Observer.\n * @return A Subscriber wrapping the (partially defined)\n * Observer represented by the given arguments.\n * @nocollapse\n * @deprecated Do not use. Will be removed in v8. There is no replacement for this\n * method, and there is no reason to be creating instances of `Subscriber` directly.\n * If you have a specific use case, please file an issue.\n */\n static create(next?: (x?: T) => void, error?: (e?: any) => void, complete?: () => void): Subscriber {\n return new SafeSubscriber(next, error, complete);\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected isStopped: boolean = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected destination: Subscriber | Observer; // this `any` is the escape hatch to erase extra type param (e.g. R)\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * There is no reason to directly create an instance of Subscriber. This type is exported for typings reasons.\n */\n constructor(destination?: Subscriber | Observer) {\n super();\n if (destination) {\n this.destination = destination;\n // Automatically chain subscriptions together here.\n // if destination is a Subscription, then it is a Subscriber.\n if (isSubscription(destination)) {\n destination.add(this);\n }\n } else {\n this.destination = EMPTY_OBSERVER;\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `next` from\n * the Observable, with a value. The Observable may call this method 0 or more\n * times.\n * @param {T} [value] The `next` value.\n * @return {void}\n */\n next(value?: T): void {\n if (this.isStopped) {\n handleStoppedNotification(nextNotification(value), this);\n } else {\n this._next(value!);\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `error` from\n * the Observable, with an attached `Error`. Notifies the Observer that\n * the Observable has experienced an error condition.\n * @param {any} [err] The `error` exception.\n * @return {void}\n */\n error(err?: any): void {\n if (this.isStopped) {\n handleStoppedNotification(errorNotification(err), this);\n } else {\n this.isStopped = true;\n this._error(err);\n }\n }\n\n /**\n * The {@link Observer} callback to receive a valueless notification of type\n * `complete` from the Observable. Notifies the Observer that the Observable\n * has finished sending push-based notifications.\n * @return {void}\n */\n complete(): void {\n if (this.isStopped) {\n handleStoppedNotification(COMPLETE_NOTIFICATION, this);\n } else {\n this.isStopped = true;\n this._complete();\n }\n }\n\n unsubscribe(): void {\n if (!this.closed) {\n this.isStopped = true;\n super.unsubscribe();\n this.destination = null!;\n }\n }\n\n protected _next(value: T): void {\n this.destination.next(value);\n }\n\n protected _error(err: any): void {\n try {\n this.destination.error(err);\n } finally {\n this.unsubscribe();\n }\n }\n\n protected _complete(): void {\n try {\n this.destination.complete();\n } finally {\n this.unsubscribe();\n }\n }\n}\n\n/**\n * This bind is captured here because we want to be able to have\n * compatibility with monoid libraries that tend to use a method named\n * `bind`. In particular, a library called Monio requires this.\n */\nconst _bind = Function.prototype.bind;\n\nfunction bind any>(fn: Fn, thisArg: any): Fn {\n return _bind.call(fn, thisArg);\n}\n\n/**\n * Internal optimization only, DO NOT EXPOSE.\n * @internal\n */\nclass ConsumerObserver implements Observer {\n constructor(private partialObserver: Partial>) {}\n\n next(value: T): void {\n const { partialObserver } = this;\n if (partialObserver.next) {\n try {\n partialObserver.next(value);\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n\n error(err: any): void {\n const { partialObserver } = this;\n if (partialObserver.error) {\n try {\n partialObserver.error(err);\n } catch (error) {\n handleUnhandledError(error);\n }\n } else {\n handleUnhandledError(err);\n }\n }\n\n complete(): void {\n const { partialObserver } = this;\n if (partialObserver.complete) {\n try {\n partialObserver.complete();\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n}\n\nexport class SafeSubscriber extends Subscriber {\n constructor(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((e?: any) => void) | null,\n complete?: (() => void) | null\n ) {\n super();\n\n let partialObserver: Partial>;\n if (isFunction(observerOrNext) || !observerOrNext) {\n // The first argument is a function, not an observer. The next\n // two arguments *could* be observers, or they could be empty.\n partialObserver = {\n next: (observerOrNext ?? undefined) as (((value: T) => void) | undefined),\n error: error ?? undefined,\n complete: complete ?? undefined,\n };\n } else {\n // The first argument is a partial observer.\n let context: any;\n if (this && config.useDeprecatedNextContext) {\n // This is a deprecated path that made `this.unsubscribe()` available in\n // next handler functions passed to subscribe. This only exists behind a flag\n // now, as it is *very* slow.\n context = Object.create(observerOrNext);\n context.unsubscribe = () => this.unsubscribe();\n partialObserver = {\n next: observerOrNext.next && bind(observerOrNext.next, context),\n error: observerOrNext.error && bind(observerOrNext.error, context),\n complete: observerOrNext.complete && bind(observerOrNext.complete, context),\n };\n } else {\n // The \"normal\" path. Just use the partial observer directly.\n partialObserver = observerOrNext;\n }\n }\n\n // Wrap the partial observer to ensure it's a full observer, and\n // make sure proper error handling is accounted for.\n this.destination = new ConsumerObserver(partialObserver);\n }\n}\n\nfunction handleUnhandledError(error: any) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n captureError(error);\n } else {\n // Ideal path, we report this as an unhandled error,\n // which is thrown on a new call stack.\n reportUnhandledError(error);\n }\n}\n\n/**\n * An error handler used when no error handler was supplied\n * to the SafeSubscriber -- meaning no error handler was supplied\n * do the `subscribe` call on our observable.\n * @param err The error to handle\n */\nfunction defaultErrorHandler(err: any) {\n throw err;\n}\n\n/**\n * A handler for notifications that cannot be sent to a stopped subscriber.\n * @param notification The notification being sent\n * @param subscriber The stopped subscriber\n */\nfunction handleStoppedNotification(notification: ObservableNotification, subscriber: Subscriber) {\n const { onStoppedNotification } = config;\n onStoppedNotification && timeoutProvider.setTimeout(() => onStoppedNotification(notification, subscriber));\n}\n\n/**\n * The observer used as a stub for subscriptions where the user did not\n * pass any arguments to `subscribe`. Comes with the default error handling\n * behavior.\n */\nexport const EMPTY_OBSERVER: Readonly> & { closed: true } = {\n closed: true,\n next: noop,\n error: defaultErrorHandler,\n complete: noop,\n};\n", "/**\n * Symbol.observable or a string \"@@observable\". Used for interop\n *\n * @deprecated We will no longer be exporting this symbol in upcoming versions of RxJS.\n * Instead polyfill and use Symbol.observable directly *or* use https://www.npmjs.com/package/symbol-observable\n */\nexport const observable: string | symbol = (() => (typeof Symbol === 'function' && Symbol.observable) || '@@observable')();\n", "/**\n * This function takes one parameter and just returns it. Simply put,\n * this is like `(x: T): T => x`.\n *\n * ## Examples\n *\n * This is useful in some cases when using things like `mergeMap`\n *\n * ```ts\n * import { interval, take, map, range, mergeMap, identity } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(5));\n *\n * const result$ = source$.pipe(\n * map(i => range(i)),\n * mergeMap(identity) // same as mergeMap(x => x)\n * );\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * Or when you want to selectively apply an operator\n *\n * ```ts\n * import { interval, take, identity } from 'rxjs';\n *\n * const shouldLimit = () => Math.random() < 0.5;\n *\n * const source$ = interval(1000);\n *\n * const result$ = source$.pipe(shouldLimit() ? take(5) : identity);\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * @param x Any value that is returned by this function\n * @returns The value passed as the first parameter to this function\n */\nexport function identity(x: T): T {\n return x;\n}\n", "import { identity } from './identity';\nimport { UnaryFunction } from '../types';\n\nexport function pipe(): typeof identity;\nexport function pipe(fn1: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction, fn3: UnaryFunction): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction,\n ...fns: UnaryFunction[]\n): UnaryFunction;\n\n/**\n * pipe() can be called on one or more functions, each of which can take one argument (\"UnaryFunction\")\n * and uses it to return a value.\n * It returns a function that takes one argument, passes it to the first UnaryFunction, and then\n * passes the result to the next one, passes that result to the next one, and so on. \n */\nexport function pipe(...fns: Array>): UnaryFunction {\n return pipeFromArray(fns);\n}\n\n/** @internal */\nexport function pipeFromArray(fns: Array>): UnaryFunction {\n if (fns.length === 0) {\n return identity as UnaryFunction;\n }\n\n if (fns.length === 1) {\n return fns[0];\n }\n\n return function piped(input: T): R {\n return fns.reduce((prev: any, fn: UnaryFunction) => fn(prev), input as any);\n };\n}\n", "import { Operator } from './Operator';\nimport { SafeSubscriber, Subscriber } from './Subscriber';\nimport { isSubscription, Subscription } from './Subscription';\nimport { TeardownLogic, OperatorFunction, Subscribable, Observer } from './types';\nimport { observable as Symbol_observable } from './symbol/observable';\nimport { pipeFromArray } from './util/pipe';\nimport { config } from './config';\nimport { isFunction } from './util/isFunction';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A representation of any set of values over any amount of time. This is the most basic building block\n * of RxJS.\n *\n * @class Observable\n */\nexport class Observable implements Subscribable {\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n source: Observable | undefined;\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n operator: Operator | undefined;\n\n /**\n * @constructor\n * @param {Function} subscribe the function that is called when the Observable is\n * initially subscribed to. This function is given a Subscriber, to which new values\n * can be `next`ed, or an `error` method can be called to raise an error, or\n * `complete` can be called to notify of a successful completion.\n */\n constructor(subscribe?: (this: Observable, subscriber: Subscriber) => TeardownLogic) {\n if (subscribe) {\n this._subscribe = subscribe;\n }\n }\n\n // HACK: Since TypeScript inherits static properties too, we have to\n // fight against TypeScript here so Subject can have a different static create signature\n /**\n * Creates a new Observable by calling the Observable constructor\n * @owner Observable\n * @method create\n * @param {Function} subscribe? the subscriber function to be passed to the Observable constructor\n * @return {Observable} a new observable\n * @nocollapse\n * @deprecated Use `new Observable()` instead. Will be removed in v8.\n */\n static create: (...args: any[]) => any = (subscribe?: (subscriber: Subscriber) => TeardownLogic) => {\n return new Observable(subscribe);\n };\n\n /**\n * Creates a new Observable, with this Observable instance as the source, and the passed\n * operator defined as the new observable's operator.\n * @method lift\n * @param operator the operator defining the operation to take on the observable\n * @return a new observable with the Operator applied\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * If you have implemented an operator using `lift`, it is recommended that you create an\n * operator by simply returning `new Observable()` directly. See \"Creating new operators from\n * scratch\" section here: https://rxjs.dev/guide/operators\n */\n lift(operator?: Operator): Observable {\n const observable = new Observable();\n observable.source = this;\n observable.operator = operator;\n return observable;\n }\n\n subscribe(observerOrNext?: Partial> | ((value: T) => void)): Subscription;\n /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */\n subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n /**\n * Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.\n *\n * Use it when you have all these Observables, but still nothing is happening.\n *\n * `subscribe` is not a regular operator, but a method that calls Observable's internal `subscribe` function. It\n * might be for example a function that you passed to Observable's constructor, but most of the time it is\n * a library implementation, which defines what will be emitted by an Observable, and when it be will emitted. This means\n * that calling `subscribe` is actually the moment when Observable starts its work, not when it is created, as it is often\n * the thought.\n *\n * Apart from starting the execution of an Observable, this method allows you to listen for values\n * that an Observable emits, as well as for when it completes or errors. You can achieve this in two\n * of the following ways.\n *\n * The first way is creating an object that implements {@link Observer} interface. It should have methods\n * defined by that interface, but note that it should be just a regular JavaScript object, which you can create\n * yourself in any way you want (ES6 class, classic function constructor, object literal etc.). In particular, do\n * not attempt to use any RxJS implementation details to create Observers - you don't need them. Remember also\n * that your object does not have to implement all methods. If you find yourself creating a method that doesn't\n * do anything, you can simply omit it. Note however, if the `error` method is not provided and an error happens,\n * it will be thrown asynchronously. Errors thrown asynchronously cannot be caught using `try`/`catch`. Instead,\n * use the {@link onUnhandledError} configuration option or use a runtime handler (like `window.onerror` or\n * `process.on('error)`) to be notified of unhandled errors. Because of this, it's recommended that you provide\n * an `error` method to avoid missing thrown errors.\n *\n * The second way is to give up on Observer object altogether and simply provide callback functions in place of its methods.\n * This means you can provide three functions as arguments to `subscribe`, where the first function is equivalent\n * of a `next` method, the second of an `error` method and the third of a `complete` method. Just as in case of an Observer,\n * if you do not need to listen for something, you can omit a function by passing `undefined` or `null`,\n * since `subscribe` recognizes these functions by where they were placed in function call. When it comes\n * to the `error` function, as with an Observer, if not provided, errors emitted by an Observable will be thrown asynchronously.\n *\n * You can, however, subscribe with no parameters at all. This may be the case where you're not interested in terminal events\n * and you also handled emissions internally by using operators (e.g. using `tap`).\n *\n * Whichever style of calling `subscribe` you use, in both cases it returns a Subscription object.\n * This object allows you to call `unsubscribe` on it, which in turn will stop the work that an Observable does and will clean\n * up all resources that an Observable used. Note that cancelling a subscription will not call `complete` callback\n * provided to `subscribe` function, which is reserved for a regular completion signal that comes from an Observable.\n *\n * Remember that callbacks provided to `subscribe` are not guaranteed to be called asynchronously.\n * It is an Observable itself that decides when these functions will be called. For example {@link of}\n * by default emits all its values synchronously. Always check documentation for how given Observable\n * will behave when subscribed and if its default behavior can be modified with a `scheduler`.\n *\n * #### Examples\n *\n * Subscribe with an {@link guide/observer Observer}\n *\n * ```ts\n * import { of } from 'rxjs';\n *\n * const sumObserver = {\n * sum: 0,\n * next(value) {\n * console.log('Adding: ' + value);\n * this.sum = this.sum + value;\n * },\n * error() {\n * // We actually could just remove this method,\n * // since we do not really care about errors right now.\n * },\n * complete() {\n * console.log('Sum equals: ' + this.sum);\n * }\n * };\n *\n * of(1, 2, 3) // Synchronously emits 1, 2, 3 and then completes.\n * .subscribe(sumObserver);\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Subscribe with functions ({@link deprecations/subscribe-arguments deprecated})\n *\n * ```ts\n * import { of } from 'rxjs'\n *\n * let sum = 0;\n *\n * of(1, 2, 3).subscribe(\n * value => {\n * console.log('Adding: ' + value);\n * sum = sum + value;\n * },\n * undefined,\n * () => console.log('Sum equals: ' + sum)\n * );\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Cancel a subscription\n *\n * ```ts\n * import { interval } from 'rxjs';\n *\n * const subscription = interval(1000).subscribe({\n * next(num) {\n * console.log(num)\n * },\n * complete() {\n * // Will not be called, even when cancelling subscription.\n * console.log('completed!');\n * }\n * });\n *\n * setTimeout(() => {\n * subscription.unsubscribe();\n * console.log('unsubscribed!');\n * }, 2500);\n *\n * // Logs:\n * // 0 after 1s\n * // 1 after 2s\n * // 'unsubscribed!' after 2.5s\n * ```\n *\n * @param {Observer|Function} observerOrNext (optional) Either an observer with methods to be called,\n * or the first of three possible handlers, which is the handler for each value emitted from the subscribed\n * Observable.\n * @param {Function} error (optional) A handler for a terminal event resulting from an error. If no error handler is provided,\n * the error will be thrown asynchronously as unhandled.\n * @param {Function} complete (optional) A handler for a terminal event resulting from successful completion.\n * @return {Subscription} a subscription reference to the registered handlers\n * @method subscribe\n */\n subscribe(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((error: any) => void) | null,\n complete?: (() => void) | null\n ): Subscription {\n const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);\n\n errorContext(() => {\n const { operator, source } = this;\n subscriber.add(\n operator\n ? // We're dealing with a subscription in the\n // operator chain to one of our lifted operators.\n operator.call(subscriber, source)\n : source\n ? // If `source` has a value, but `operator` does not, something that\n // had intimate knowledge of our API, like our `Subject`, must have\n // set it. We're going to just call `_subscribe` directly.\n this._subscribe(subscriber)\n : // In all other cases, we're likely wrapping a user-provided initializer\n // function, so we need to catch errors and handle them appropriately.\n this._trySubscribe(subscriber)\n );\n });\n\n return subscriber;\n }\n\n /** @internal */\n protected _trySubscribe(sink: Subscriber): TeardownLogic {\n try {\n return this._subscribe(sink);\n } catch (err) {\n // We don't need to return anything in this case,\n // because it's just going to try to `add()` to a subscription\n // above.\n sink.error(err);\n }\n }\n\n /**\n * Used as a NON-CANCELLABLE means of subscribing to an observable, for use with\n * APIs that expect promises, like `async/await`. You cannot unsubscribe from this.\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * #### Example\n *\n * ```ts\n * import { interval, take } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(4));\n *\n * async function getTotal() {\n * let total = 0;\n *\n * await source$.forEach(value => {\n * total += value;\n * console.log('observable -> ' + value);\n * });\n *\n * return total;\n * }\n *\n * getTotal().then(\n * total => console.log('Total: ' + total)\n * );\n *\n * // Expected:\n * // 'observable -> 0'\n * // 'observable -> 1'\n * // 'observable -> 2'\n * // 'observable -> 3'\n * // 'Total: 6'\n * ```\n *\n * @param next a handler for each value emitted by the observable\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n */\n forEach(next: (value: T) => void): Promise;\n\n /**\n * @param next a handler for each value emitted by the observable\n * @param promiseCtor a constructor function used to instantiate the Promise\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n * @deprecated Passing a Promise constructor will no longer be available\n * in upcoming versions of RxJS. This is because it adds weight to the library, for very\n * little benefit. If you need this functionality, it is recommended that you either\n * polyfill Promise, or you create an adapter to convert the returned native promise\n * to whatever promise implementation you wanted. Will be removed in v8.\n */\n forEach(next: (value: T) => void, promiseCtor: PromiseConstructorLike): Promise;\n\n forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n const subscriber = new SafeSubscriber({\n next: (value) => {\n try {\n next(value);\n } catch (err) {\n reject(err);\n subscriber.unsubscribe();\n }\n },\n error: reject,\n complete: resolve,\n });\n this.subscribe(subscriber);\n }) as Promise;\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): TeardownLogic {\n return this.source?.subscribe(subscriber);\n }\n\n /**\n * An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable\n * @method Symbol.observable\n * @return {Observable} this instance of the observable\n */\n [Symbol_observable]() {\n return this;\n }\n\n /* tslint:disable:max-line-length */\n pipe(): Observable;\n pipe(op1: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction,\n ...operations: OperatorFunction[]\n ): Observable;\n /* tslint:enable:max-line-length */\n\n /**\n * Used to stitch together functional operators into a chain.\n * @method pipe\n * @return {Observable} the Observable result of all of the operators having\n * been called in the order they were passed in.\n *\n * ## Example\n *\n * ```ts\n * import { interval, filter, map, scan } from 'rxjs';\n *\n * interval(1000)\n * .pipe(\n * filter(x => x % 2 === 0),\n * map(x => x + x),\n * scan((acc, x) => acc + x)\n * )\n * .subscribe(x => console.log(x));\n * ```\n */\n pipe(...operations: OperatorFunction[]): Observable {\n return pipeFromArray(operations)(this);\n }\n\n /* tslint:disable:max-line-length */\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: typeof Promise): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: PromiseConstructorLike): Promise;\n /* tslint:enable:max-line-length */\n\n /**\n * Subscribe to this Observable and get a Promise resolving on\n * `complete` with the last emission (if any).\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * @method toPromise\n * @param [promiseCtor] a constructor function used to instantiate\n * the Promise\n * @return A Promise that resolves with the last value emit, or\n * rejects on an error. If there were no emissions, Promise\n * resolves with undefined.\n * @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise\n */\n toPromise(promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n let value: T | undefined;\n this.subscribe(\n (x: T) => (value = x),\n (err: any) => reject(err),\n () => resolve(value)\n );\n }) as Promise;\n }\n}\n\n/**\n * Decides between a passed promise constructor from consuming code,\n * A default configured promise constructor, and the native promise\n * constructor and returns it. If nothing can be found, it will throw\n * an error.\n * @param promiseCtor The optional promise constructor to passed by consuming code\n */\nfunction getPromiseCtor(promiseCtor: PromiseConstructorLike | undefined) {\n return promiseCtor ?? config.Promise ?? Promise;\n}\n\nfunction isObserver(value: any): value is Observer {\n return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);\n}\n\nfunction isSubscriber(value: any): value is Subscriber {\n return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));\n}\n", "import { Observable } from '../Observable';\nimport { Subscriber } from '../Subscriber';\nimport { OperatorFunction } from '../types';\nimport { isFunction } from './isFunction';\n\n/**\n * Used to determine if an object is an Observable with a lift function.\n */\nexport function hasLift(source: any): source is { lift: InstanceType['lift'] } {\n return isFunction(source?.lift);\n}\n\n/**\n * Creates an `OperatorFunction`. Used to define operators throughout the library in a concise way.\n * @param init The logic to connect the liftedSource to the subscriber at the moment of subscription.\n */\nexport function operate(\n init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void\n): OperatorFunction {\n return (source: Observable) => {\n if (hasLift(source)) {\n return source.lift(function (this: Subscriber, liftedSource: Observable) {\n try {\n return init(liftedSource, this);\n } catch (err) {\n this.error(err);\n }\n });\n }\n throw new TypeError('Unable to lift unknown Observable type');\n };\n}\n", "import { Subscriber } from '../Subscriber';\n\n/**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional teardown logic here. This will only be called on teardown if the\n * subscriber itself is not already closed. This is called after all other teardown logic is executed.\n */\nexport function createOperatorSubscriber(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n onFinalize?: () => void\n): Subscriber {\n return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);\n}\n\n/**\n * A generic helper for allowing operators to be created with a Subscriber and\n * use closures to capture necessary state from the operator function itself.\n */\nexport class OperatorSubscriber extends Subscriber {\n /**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional finalization logic here. This will only be called on finalization if the\n * subscriber itself is not already closed. This is called after all other finalization logic is executed.\n * @param shouldUnsubscribe An optional check to see if an unsubscribe call should truly unsubscribe.\n * NOTE: This currently **ONLY** exists to support the strange behavior of {@link groupBy}, where unsubscription\n * to the resulting observable does not actually disconnect from the source if there are active subscriptions\n * to any grouped observable. (DO NOT EXPOSE OR USE EXTERNALLY!!!)\n */\n constructor(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n private onFinalize?: () => void,\n private shouldUnsubscribe?: () => boolean\n ) {\n // It's important - for performance reasons - that all of this class's\n // members are initialized and that they are always initialized in the same\n // order. This will ensure that all OperatorSubscriber instances have the\n // same hidden class in V8. This, in turn, will help keep the number of\n // hidden classes involved in property accesses within the base class as\n // low as possible. If the number of hidden classes involved exceeds four,\n // the property accesses will become megamorphic and performance penalties\n // will be incurred - i.e. inline caches won't be used.\n //\n // The reasons for ensuring all instances have the same hidden class are\n // further discussed in this blog post from Benedikt Meurer:\n // https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/\n super(destination);\n this._next = onNext\n ? function (this: OperatorSubscriber, value: T) {\n try {\n onNext(value);\n } catch (err) {\n destination.error(err);\n }\n }\n : super._next;\n this._error = onError\n ? function (this: OperatorSubscriber, err: any) {\n try {\n onError(err);\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._error;\n this._complete = onComplete\n ? function (this: OperatorSubscriber) {\n try {\n onComplete();\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._complete;\n }\n\n unsubscribe() {\n if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {\n const { closed } = this;\n super.unsubscribe();\n // Execute additional teardown if we have any and we didn't already do so.\n !closed && this.onFinalize?.();\n }\n }\n}\n", "import { Subscription } from '../Subscription';\n\ninterface AnimationFrameProvider {\n schedule(callback: FrameRequestCallback): Subscription;\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n delegate:\n | {\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n }\n | undefined;\n}\n\nexport const animationFrameProvider: AnimationFrameProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n schedule(callback) {\n let request = requestAnimationFrame;\n let cancel: typeof cancelAnimationFrame | undefined = cancelAnimationFrame;\n const { delegate } = animationFrameProvider;\n if (delegate) {\n request = delegate.requestAnimationFrame;\n cancel = delegate.cancelAnimationFrame;\n }\n const handle = request((timestamp) => {\n // Clear the cancel function. The request has been fulfilled, so\n // attempting to cancel the request upon unsubscription would be\n // pointless.\n cancel = undefined;\n callback(timestamp);\n });\n return new Subscription(() => cancel?.(handle));\n },\n requestAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.requestAnimationFrame || requestAnimationFrame)(...args);\n },\n cancelAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.cancelAnimationFrame || cancelAnimationFrame)(...args);\n },\n delegate: undefined,\n};\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface ObjectUnsubscribedError extends Error {}\n\nexport interface ObjectUnsubscribedErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (): ObjectUnsubscribedError;\n}\n\n/**\n * An error thrown when an action is invalid because the object has been\n * unsubscribed.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n *\n * @class ObjectUnsubscribedError\n */\nexport const ObjectUnsubscribedError: ObjectUnsubscribedErrorCtor = createErrorClass(\n (_super) =>\n function ObjectUnsubscribedErrorImpl(this: any) {\n _super(this);\n this.name = 'ObjectUnsubscribedError';\n this.message = 'object unsubscribed';\n }\n);\n", "import { Operator } from './Operator';\nimport { Observable } from './Observable';\nimport { Subscriber } from './Subscriber';\nimport { Subscription, EMPTY_SUBSCRIPTION } from './Subscription';\nimport { Observer, SubscriptionLike, TeardownLogic } from './types';\nimport { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';\nimport { arrRemove } from './util/arrRemove';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A Subject is a special type of Observable that allows values to be\n * multicasted to many Observers. Subjects are like EventEmitters.\n *\n * Every Subject is an Observable and an Observer. You can subscribe to a\n * Subject, and you can call next to feed values as well as error and complete.\n */\nexport class Subject extends Observable implements SubscriptionLike {\n closed = false;\n\n private currentObservers: Observer[] | null = null;\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n observers: Observer[] = [];\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n isStopped = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n hasError = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n thrownError: any = null;\n\n /**\n * Creates a \"subject\" by basically gluing an observer to an observable.\n *\n * @nocollapse\n * @deprecated Recommended you do not use. Will be removed at some point in the future. Plans for replacement still under discussion.\n */\n static create: (...args: any[]) => any = (destination: Observer, source: Observable): AnonymousSubject => {\n return new AnonymousSubject(destination, source);\n };\n\n constructor() {\n // NOTE: This must be here to obscure Observable's constructor.\n super();\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n lift(operator: Operator): Observable {\n const subject = new AnonymousSubject(this, this);\n subject.operator = operator as any;\n return subject as any;\n }\n\n /** @internal */\n protected _throwIfClosed() {\n if (this.closed) {\n throw new ObjectUnsubscribedError();\n }\n }\n\n next(value: T) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n if (!this.currentObservers) {\n this.currentObservers = Array.from(this.observers);\n }\n for (const observer of this.currentObservers) {\n observer.next(value);\n }\n }\n });\n }\n\n error(err: any) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.hasError = this.isStopped = true;\n this.thrownError = err;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.error(err);\n }\n }\n });\n }\n\n complete() {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.isStopped = true;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.complete();\n }\n }\n });\n }\n\n unsubscribe() {\n this.isStopped = this.closed = true;\n this.observers = this.currentObservers = null!;\n }\n\n get observed() {\n return this.observers?.length > 0;\n }\n\n /** @internal */\n protected _trySubscribe(subscriber: Subscriber): TeardownLogic {\n this._throwIfClosed();\n return super._trySubscribe(subscriber);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._checkFinalizedStatuses(subscriber);\n return this._innerSubscribe(subscriber);\n }\n\n /** @internal */\n protected _innerSubscribe(subscriber: Subscriber) {\n const { hasError, isStopped, observers } = this;\n if (hasError || isStopped) {\n return EMPTY_SUBSCRIPTION;\n }\n this.currentObservers = null;\n observers.push(subscriber);\n return new Subscription(() => {\n this.currentObservers = null;\n arrRemove(observers, subscriber);\n });\n }\n\n /** @internal */\n protected _checkFinalizedStatuses(subscriber: Subscriber) {\n const { hasError, thrownError, isStopped } = this;\n if (hasError) {\n subscriber.error(thrownError);\n } else if (isStopped) {\n subscriber.complete();\n }\n }\n\n /**\n * Creates a new Observable with this Subject as the source. You can do this\n * to create custom Observer-side logic of the Subject and conceal it from\n * code that uses the Observable.\n * @return {Observable} Observable that the Subject casts to\n */\n asObservable(): Observable {\n const observable: any = new Observable();\n observable.source = this;\n return observable;\n }\n}\n\n/**\n * @class AnonymousSubject\n */\nexport class AnonymousSubject extends Subject {\n constructor(\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n public destination?: Observer,\n source?: Observable\n ) {\n super();\n this.source = source;\n }\n\n next(value: T) {\n this.destination?.next?.(value);\n }\n\n error(err: any) {\n this.destination?.error?.(err);\n }\n\n complete() {\n this.destination?.complete?.();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n return this.source?.subscribe(subscriber) ?? EMPTY_SUBSCRIPTION;\n }\n}\n", "import { Subject } from './Subject';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\n\n/**\n * A variant of Subject that requires an initial value and emits its current\n * value whenever it is subscribed to.\n *\n * @class BehaviorSubject\n */\nexport class BehaviorSubject extends Subject {\n constructor(private _value: T) {\n super();\n }\n\n get value(): T {\n return this.getValue();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n const subscription = super._subscribe(subscriber);\n !subscription.closed && subscriber.next(this._value);\n return subscription;\n }\n\n getValue(): T {\n const { hasError, thrownError, _value } = this;\n if (hasError) {\n throw thrownError;\n }\n this._throwIfClosed();\n return _value;\n }\n\n next(value: T): void {\n super.next((this._value = value));\n }\n}\n", "import { TimestampProvider } from '../types';\n\ninterface DateTimestampProvider extends TimestampProvider {\n delegate: TimestampProvider | undefined;\n}\n\nexport const dateTimestampProvider: DateTimestampProvider = {\n now() {\n // Use the variable rather than `this` so that the function can be called\n // without being bound to the provider.\n return (dateTimestampProvider.delegate || Date).now();\n },\n delegate: undefined,\n};\n", "import { Subject } from './Subject';\nimport { TimestampProvider } from './types';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * A variant of {@link Subject} that \"replays\" old values to new subscribers by emitting them when they first subscribe.\n *\n * `ReplaySubject` has an internal buffer that will store a specified number of values that it has observed. Like `Subject`,\n * `ReplaySubject` \"observes\" values by having them passed to its `next` method. When it observes a value, it will store that\n * value for a time determined by the configuration of the `ReplaySubject`, as passed to its constructor.\n *\n * When a new subscriber subscribes to the `ReplaySubject` instance, it will synchronously emit all values in its buffer in\n * a First-In-First-Out (FIFO) manner. The `ReplaySubject` will also complete, if it has observed completion; and it will\n * error if it has observed an error.\n *\n * There are two main configuration items to be concerned with:\n *\n * 1. `bufferSize` - This will determine how many items are stored in the buffer, defaults to infinite.\n * 2. `windowTime` - The amount of time to hold a value in the buffer before removing it from the buffer.\n *\n * Both configurations may exist simultaneously. So if you would like to buffer a maximum of 3 values, as long as the values\n * are less than 2 seconds old, you could do so with a `new ReplaySubject(3, 2000)`.\n *\n * ### Differences with BehaviorSubject\n *\n * `BehaviorSubject` is similar to `new ReplaySubject(1)`, with a couple of exceptions:\n *\n * 1. `BehaviorSubject` comes \"primed\" with a single value upon construction.\n * 2. `ReplaySubject` will replay values, even after observing an error, where `BehaviorSubject` will not.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n * @see {@link shareReplay}\n */\nexport class ReplaySubject extends Subject {\n private _buffer: (T | number)[] = [];\n private _infiniteTimeWindow = true;\n\n /**\n * @param bufferSize The size of the buffer to replay on subscription\n * @param windowTime The amount of time the buffered items will stay buffered\n * @param timestampProvider An object with a `now()` method that provides the current timestamp. This is used to\n * calculate the amount of time something has been buffered.\n */\n constructor(\n private _bufferSize = Infinity,\n private _windowTime = Infinity,\n private _timestampProvider: TimestampProvider = dateTimestampProvider\n ) {\n super();\n this._infiniteTimeWindow = _windowTime === Infinity;\n this._bufferSize = Math.max(1, _bufferSize);\n this._windowTime = Math.max(1, _windowTime);\n }\n\n next(value: T): void {\n const { isStopped, _buffer, _infiniteTimeWindow, _timestampProvider, _windowTime } = this;\n if (!isStopped) {\n _buffer.push(value);\n !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);\n }\n this._trimBuffer();\n super.next(value);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._trimBuffer();\n\n const subscription = this._innerSubscribe(subscriber);\n\n const { _infiniteTimeWindow, _buffer } = this;\n // We use a copy here, so reentrant code does not mutate our array while we're\n // emitting it to a new subscriber.\n const copy = _buffer.slice();\n for (let i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {\n subscriber.next(copy[i] as T);\n }\n\n this._checkFinalizedStatuses(subscriber);\n\n return subscription;\n }\n\n private _trimBuffer() {\n const { _bufferSize, _timestampProvider, _buffer, _infiniteTimeWindow } = this;\n // If we don't have an infinite buffer size, and we're over the length,\n // use splice to truncate the old buffer values off. Note that we have to\n // double the size for instances where we're not using an infinite time window\n // because we're storing the values and the timestamps in the same array.\n const adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;\n _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);\n\n // Now, if we're not in an infinite time window, remove all values where the time is\n // older than what is allowed.\n if (!_infiniteTimeWindow) {\n const now = _timestampProvider.now();\n let last = 0;\n // Search the array for the first timestamp that isn't expired and\n // truncate the buffer up to that point.\n for (let i = 1; i < _buffer.length && (_buffer[i] as number) <= now; i += 2) {\n last = i;\n }\n last && _buffer.splice(0, last + 1);\n }\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Subscription } from '../Subscription';\nimport { SchedulerAction } from '../types';\n\n/**\n * A unit of work to be executed in a `scheduler`. An action is typically\n * created from within a {@link SchedulerLike} and an RxJS user does not need to concern\n * themselves about creating and manipulating an Action.\n *\n * ```ts\n * class Action extends Subscription {\n * new (scheduler: Scheduler, work: (state?: T) => void);\n * schedule(state?: T, delay: number = 0): Subscription;\n * }\n * ```\n *\n * @class Action\n */\nexport class Action extends Subscription {\n constructor(scheduler: Scheduler, work: (this: SchedulerAction, state?: T) => void) {\n super();\n }\n /**\n * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed\n * some context object, `state`. May happen at some point in the future,\n * according to the `delay` parameter, if specified.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler.\n * @return {void}\n */\n public schedule(state?: T, delay: number = 0): Subscription {\n return this;\n }\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetIntervalFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearIntervalFunction = (handle: TimerHandle) => void;\n\ninterface IntervalProvider {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n delegate:\n | {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n }\n | undefined;\n}\n\nexport const intervalProvider: IntervalProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setInterval(handler: () => void, timeout?: number, ...args) {\n const { delegate } = intervalProvider;\n if (delegate?.setInterval) {\n return delegate.setInterval(handler, timeout, ...args);\n }\n return setInterval(handler, timeout, ...args);\n },\n clearInterval(handle) {\n const { delegate } = intervalProvider;\n return (delegate?.clearInterval || clearInterval)(handle as any);\n },\n delegate: undefined,\n};\n", "import { Action } from './Action';\nimport { SchedulerAction } from '../types';\nimport { Subscription } from '../Subscription';\nimport { AsyncScheduler } from './AsyncScheduler';\nimport { intervalProvider } from './intervalProvider';\nimport { arrRemove } from '../util/arrRemove';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncAction extends Action {\n public id: TimerHandle | undefined;\n public state?: T;\n // @ts-ignore: Property has no initializer and is not definitely assigned\n public delay: number;\n protected pending: boolean = false;\n\n constructor(protected scheduler: AsyncScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (this.closed) {\n return this;\n }\n\n // Always replace the current state with the new state.\n this.state = state;\n\n const id = this.id;\n const scheduler = this.scheduler;\n\n //\n // Important implementation note:\n //\n // Actions only execute once by default, unless rescheduled from within the\n // scheduled callback. This allows us to implement single and repeat\n // actions via the same code path, without adding API surface area, as well\n // as mimic traditional recursion but across asynchronous boundaries.\n //\n // However, JS runtimes and timers distinguish between intervals achieved by\n // serial `setTimeout` calls vs. a single `setInterval` call. An interval of\n // serial `setTimeout` calls can be individually delayed, which delays\n // scheduling the next `setTimeout`, and so on. `setInterval` attempts to\n // guarantee the interval callback will be invoked more precisely to the\n // interval period, regardless of load.\n //\n // Therefore, we use `setInterval` to schedule single and repeat actions.\n // If the action reschedules itself with the same delay, the interval is not\n // canceled. If the action doesn't reschedule, or reschedules with a\n // different delay, the interval will be canceled after scheduled callback\n // execution.\n //\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, delay);\n }\n\n // Set the pending flag indicating that this action has been scheduled, or\n // has recursively rescheduled itself.\n this.pending = true;\n\n this.delay = delay;\n // If this action has already an async Id, don't request a new one.\n this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay);\n\n return this;\n }\n\n protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle {\n return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);\n }\n\n protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined {\n // If this action is rescheduled with the same delay time, don't clear the interval id.\n if (delay != null && this.delay === delay && this.pending === false) {\n return id;\n }\n // Otherwise, if the action's delay time is different from the current delay,\n // or the action has been rescheduled before it's executed, clear the interval id\n if (id != null) {\n intervalProvider.clearInterval(id);\n }\n\n return undefined;\n }\n\n /**\n * Immediately executes this action and the `work` it contains.\n * @return {any}\n */\n public execute(state: T, delay: number): any {\n if (this.closed) {\n return new Error('executing a cancelled action');\n }\n\n this.pending = false;\n const error = this._execute(state, delay);\n if (error) {\n return error;\n } else if (this.pending === false && this.id != null) {\n // Dequeue if the action didn't reschedule itself. Don't call\n // unsubscribe(), because the action could reschedule later.\n // For example:\n // ```\n // scheduler.schedule(function doWork(counter) {\n // /* ... I'm a busy worker bee ... */\n // var originalAction = this;\n // /* wait 100ms before rescheduling the action */\n // setTimeout(function () {\n // originalAction.schedule(counter + 1);\n // }, 100);\n // }, 1000);\n // ```\n this.id = this.recycleAsyncId(this.scheduler, this.id, null);\n }\n }\n\n protected _execute(state: T, _delay: number): any {\n let errored: boolean = false;\n let errorValue: any;\n try {\n this.work(state);\n } catch (e) {\n errored = true;\n // HACK: Since code elsewhere is relying on the \"truthiness\" of the\n // return here, we can't have it return \"\" or 0 or false.\n // TODO: Clean this up when we refactor schedulers mid-version-8 or so.\n errorValue = e ? e : new Error('Scheduled action threw falsy error');\n }\n if (errored) {\n this.unsubscribe();\n return errorValue;\n }\n }\n\n unsubscribe() {\n if (!this.closed) {\n const { id, scheduler } = this;\n const { actions } = scheduler;\n\n this.work = this.state = this.scheduler = null!;\n this.pending = false;\n\n arrRemove(actions, this);\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, null);\n }\n\n this.delay = null!;\n super.unsubscribe();\n }\n }\n}\n", "import { Action } from './scheduler/Action';\nimport { Subscription } from './Subscription';\nimport { SchedulerLike, SchedulerAction } from './types';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * An execution context and a data structure to order tasks and schedule their\n * execution. Provides a notion of (potentially virtual) time, through the\n * `now()` getter method.\n *\n * Each unit of work in a Scheduler is called an `Action`.\n *\n * ```ts\n * class Scheduler {\n * now(): number;\n * schedule(work, delay?, state?): Subscription;\n * }\n * ```\n *\n * @class Scheduler\n * @deprecated Scheduler is an internal implementation detail of RxJS, and\n * should not be used directly. Rather, create your own class and implement\n * {@link SchedulerLike}. Will be made internal in v8.\n */\nexport class Scheduler implements SchedulerLike {\n public static now: () => number = dateTimestampProvider.now;\n\n constructor(private schedulerActionCtor: typeof Action, now: () => number = Scheduler.now) {\n this.now = now;\n }\n\n /**\n * A getter method that returns a number representing the current time\n * (at the time this function was called) according to the scheduler's own\n * internal clock.\n * @return {number} A number that represents the current time. May or may not\n * have a relation to wall-clock time. May or may not refer to a time unit\n * (e.g. milliseconds).\n */\n public now: () => number;\n\n /**\n * Schedules a function, `work`, for execution. May happen at some point in\n * the future, according to the `delay` parameter, if specified. May be passed\n * some context object, `state`, which will be passed to the `work` function.\n *\n * The given arguments will be processed an stored as an Action object in a\n * queue of actions.\n *\n * @param {function(state: ?T): ?Subscription} work A function representing a\n * task, or some unit of work to be executed by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler itself.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @return {Subscription} A subscription in order to be able to unsubscribe\n * the scheduled work.\n */\n public schedule(work: (this: SchedulerAction, state?: T) => void, delay: number = 0, state?: T): Subscription {\n return new this.schedulerActionCtor(this, work).schedule(state, delay);\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Action } from './Action';\nimport { AsyncAction } from './AsyncAction';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncScheduler extends Scheduler {\n public actions: Array> = [];\n /**\n * A flag to indicate whether the Scheduler is currently executing a batch of\n * queued actions.\n * @type {boolean}\n * @internal\n */\n public _active: boolean = false;\n /**\n * An internal ID used to track the latest asynchronous task such as those\n * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and\n * others.\n * @type {any}\n * @internal\n */\n public _scheduled: TimerHandle | undefined;\n\n constructor(SchedulerAction: typeof Action, now: () => number = Scheduler.now) {\n super(SchedulerAction, now);\n }\n\n public flush(action: AsyncAction): void {\n const { actions } = this;\n\n if (this._active) {\n actions.push(action);\n return;\n }\n\n let error: any;\n this._active = true;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions.shift()!)); // exhaust the scheduler queue\n\n this._active = false;\n\n if (error) {\n while ((action = actions.shift()!)) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\n/**\n *\n * Async Scheduler\n *\n * Schedule task as if you used setTimeout(task, duration)\n *\n * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript\n * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating\n * in intervals.\n *\n * If you just want to \"defer\" task, that is to perform it right after currently\n * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`),\n * better choice will be the {@link asapScheduler} scheduler.\n *\n * ## Examples\n * Use async scheduler to delay task\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * const task = () => console.log('it works!');\n *\n * asyncScheduler.schedule(task, 2000);\n *\n * // After 2 seconds logs:\n * // \"it works!\"\n * ```\n *\n * Use async scheduler to repeat task in intervals\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * function task(state) {\n * console.log(state);\n * this.schedule(state + 1, 1000); // `this` references currently executing Action,\n * // which we reschedule with new state and delay\n * }\n *\n * asyncScheduler.schedule(task, 3000, 0);\n *\n * // Logs:\n * // 0 after 3s\n * // 1 after 4s\n * // 2 after 5s\n * // 3 after 6s\n * ```\n */\n\nexport const asyncScheduler = new AsyncScheduler(AsyncAction);\n\n/**\n * @deprecated Renamed to {@link asyncScheduler}. Will be removed in v8.\n */\nexport const async = asyncScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { Subscription } from '../Subscription';\nimport { QueueScheduler } from './QueueScheduler';\nimport { SchedulerAction } from '../types';\nimport { TimerHandle } from './timerHandle';\n\nexport class QueueAction extends AsyncAction {\n constructor(protected scheduler: QueueScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (delay > 0) {\n return super.schedule(state, delay);\n }\n this.delay = delay;\n this.state = state;\n this.scheduler.flush(this);\n return this;\n }\n\n public execute(state: T, delay: number): any {\n return delay > 0 || this.closed ? super.execute(state, delay) : this._execute(state, delay);\n }\n\n protected requestAsyncId(scheduler: QueueScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n\n if ((delay != null && delay > 0) || (delay == null && this.delay > 0)) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n\n // Otherwise flush the scheduler starting with this action.\n scheduler.flush(this);\n\n // HACK: In the past, this was returning `void`. However, `void` isn't a valid\n // `TimerHandle`, and generally the return value here isn't really used. So the\n // compromise is to return `0` which is both \"falsy\" and a valid `TimerHandle`,\n // as opposed to refactoring every other instanceo of `requestAsyncId`.\n return 0;\n }\n}\n", "import { AsyncScheduler } from './AsyncScheduler';\n\nexport class QueueScheduler extends AsyncScheduler {\n}\n", "import { QueueAction } from './QueueAction';\nimport { QueueScheduler } from './QueueScheduler';\n\n/**\n *\n * Queue Scheduler\n *\n * Put every next task on a queue, instead of executing it immediately\n *\n * `queue` scheduler, when used with delay, behaves the same as {@link asyncScheduler} scheduler.\n *\n * When used without delay, it schedules given task synchronously - executes it right when\n * it is scheduled. However when called recursively, that is when inside the scheduled task,\n * another task is scheduled with queue scheduler, instead of executing immediately as well,\n * that task will be put on a queue and wait for current one to finish.\n *\n * This means that when you execute task with `queue` scheduler, you are sure it will end\n * before any other task scheduled with that scheduler will start.\n *\n * ## Examples\n * Schedule recursively first, then do something\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(() => {\n * queueScheduler.schedule(() => console.log('second')); // will not happen now, but will be put on a queue\n *\n * console.log('first');\n * });\n *\n * // Logs:\n * // \"first\"\n * // \"second\"\n * ```\n *\n * Reschedule itself recursively\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(function(state) {\n * if (state !== 0) {\n * console.log('before', state);\n * this.schedule(state - 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * console.log('after', state);\n * }\n * }, 0, 3);\n *\n * // In scheduler that runs recursively, you would expect:\n * // \"before\", 3\n * // \"before\", 2\n * // \"before\", 1\n * // \"after\", 1\n * // \"after\", 2\n * // \"after\", 3\n *\n * // But with queue it logs:\n * // \"before\", 3\n * // \"after\", 3\n * // \"before\", 2\n * // \"after\", 2\n * // \"before\", 1\n * // \"after\", 1\n * ```\n */\n\nexport const queueScheduler = new QueueScheduler(QueueAction);\n\n/**\n * @deprecated Renamed to {@link queueScheduler}. Will be removed in v8.\n */\nexport const queue = queueScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\nimport { SchedulerAction } from '../types';\nimport { animationFrameProvider } from './animationFrameProvider';\nimport { TimerHandle } from './timerHandle';\n\nexport class AnimationFrameAction extends AsyncAction {\n constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay is greater than 0, request as an async action.\n if (delay !== null && delay > 0) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n // Push the action to the end of the scheduler queue.\n scheduler.actions.push(this);\n // If an animation frame has already been requested, don't request another\n // one. If an animation frame hasn't been requested yet, request one. Return\n // the current animation frame request id.\n return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined)));\n }\n\n protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n if (delay != null ? delay > 0 : this.delay > 0) {\n return super.recycleAsyncId(scheduler, id, delay);\n }\n // If the scheduler queue has no remaining actions with the same async id,\n // cancel the requested animation frame and set the scheduled flag to\n // undefined so the next AnimationFrameAction will request its own.\n const { actions } = scheduler;\n if (id != null && actions[actions.length - 1]?.id !== id) {\n animationFrameProvider.cancelAnimationFrame(id as number);\n scheduler._scheduled = undefined;\n }\n // Return undefined so the action knows to request a new async id if it's rescheduled.\n return undefined;\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\nexport class AnimationFrameScheduler extends AsyncScheduler {\n public flush(action?: AsyncAction): void {\n this._active = true;\n // The async id that effects a call to flush is stored in _scheduled.\n // Before executing an action, it's necessary to check the action's async\n // id to determine whether it's supposed to be executed in the current\n // flush.\n // Previous implementations of this method used a count to determine this,\n // but that was unsound, as actions that are unsubscribed - i.e. cancelled -\n // are removed from the actions array and that can shift actions that are\n // scheduled to be executed in a subsequent flush into positions at which\n // they are executed within the current flush.\n const flushId = this._scheduled;\n this._scheduled = undefined;\n\n const { actions } = this;\n let error: any;\n action = action || actions.shift()!;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions[0]) && action.id === flushId && actions.shift());\n\n this._active = false;\n\n if (error) {\n while ((action = actions[0]) && action.id === flushId && actions.shift()) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AnimationFrameAction } from './AnimationFrameAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\n\n/**\n *\n * Animation Frame Scheduler\n *\n * Perform task when `window.requestAnimationFrame` would fire\n *\n * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler\n * behaviour.\n *\n * Without delay, `animationFrame` scheduler can be used to create smooth browser animations.\n * It makes sure scheduled task will happen just before next browser content repaint,\n * thus performing animations as efficiently as possible.\n *\n * ## Example\n * Schedule div height animation\n * ```ts\n * // html:
\n * import { animationFrameScheduler } from 'rxjs';\n *\n * const div = document.querySelector('div');\n *\n * animationFrameScheduler.schedule(function(height) {\n * div.style.height = height + \"px\";\n *\n * this.schedule(height + 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * }, 0, 0);\n *\n * // You will see a div element growing in height\n * ```\n */\n\nexport const animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction);\n\n/**\n * @deprecated Renamed to {@link animationFrameScheduler}. Will be removed in v8.\n */\nexport const animationFrame = animationFrameScheduler;\n", "import { Observable } from '../Observable';\nimport { SchedulerLike } from '../types';\n\n/**\n * A simple Observable that emits no items to the Observer and immediately\n * emits a complete notification.\n *\n * Just emits 'complete', and nothing else.\n *\n * ![](empty.png)\n *\n * A simple Observable that only emits the complete notification. It can be used\n * for composing with other Observables, such as in a {@link mergeMap}.\n *\n * ## Examples\n *\n * Log complete notification\n *\n * ```ts\n * import { EMPTY } from 'rxjs';\n *\n * EMPTY.subscribe({\n * next: () => console.log('Next'),\n * complete: () => console.log('Complete!')\n * });\n *\n * // Outputs\n * // Complete!\n * ```\n *\n * Emit the number 7, then complete\n *\n * ```ts\n * import { EMPTY, startWith } from 'rxjs';\n *\n * const result = EMPTY.pipe(startWith(7));\n * result.subscribe(x => console.log(x));\n *\n * // Outputs\n * // 7\n * ```\n *\n * Map and flatten only odd numbers to the sequence `'a'`, `'b'`, `'c'`\n *\n * ```ts\n * import { interval, mergeMap, of, EMPTY } from 'rxjs';\n *\n * const interval$ = interval(1000);\n * const result = interval$.pipe(\n * mergeMap(x => x % 2 === 1 ? of('a', 'b', 'c') : EMPTY),\n * );\n * result.subscribe(x => console.log(x));\n *\n * // Results in the following to the console:\n * // x is equal to the count on the interval, e.g. (0, 1, 2, 3, ...)\n * // x will occur every 1000ms\n * // if x % 2 is equal to 1, print a, b, c (each on its own)\n * // if x % 2 is not equal to 1, nothing will be output\n * ```\n *\n * @see {@link Observable}\n * @see {@link NEVER}\n * @see {@link of}\n * @see {@link throwError}\n */\nexport const EMPTY = new Observable((subscriber) => subscriber.complete());\n\n/**\n * @param scheduler A {@link SchedulerLike} to use for scheduling\n * the emission of the complete notification.\n * @deprecated Replaced with the {@link EMPTY} constant or {@link scheduled} (e.g. `scheduled([], scheduler)`). Will be removed in v8.\n */\nexport function empty(scheduler?: SchedulerLike) {\n return scheduler ? emptyScheduled(scheduler) : EMPTY;\n}\n\nfunction emptyScheduled(scheduler: SchedulerLike) {\n return new Observable((subscriber) => scheduler.schedule(() => subscriber.complete()));\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport function isScheduler(value: any): value is SchedulerLike {\n return value && isFunction(value.schedule);\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\nimport { isScheduler } from './isScheduler';\n\nfunction last(arr: T[]): T | undefined {\n return arr[arr.length - 1];\n}\n\nexport function popResultSelector(args: any[]): ((...args: unknown[]) => unknown) | undefined {\n return isFunction(last(args)) ? args.pop() : undefined;\n}\n\nexport function popScheduler(args: any[]): SchedulerLike | undefined {\n return isScheduler(last(args)) ? args.pop() : undefined;\n}\n\nexport function popNumber(args: any[], defaultValue: number): number {\n return typeof last(args) === 'number' ? args.pop()! : defaultValue;\n}\n", "export const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number' && typeof x !== 'function');", "import { isFunction } from \"./isFunction\";\n\n/**\n * Tests to see if the object is \"thennable\".\n * @param value the object to test\n */\nexport function isPromise(value: any): value is PromiseLike {\n return isFunction(value?.then);\n}\n", "import { InteropObservable } from '../types';\nimport { observable as Symbol_observable } from '../symbol/observable';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being Observable (but not necessary an Rx Observable) */\nexport function isInteropObservable(input: any): input is InteropObservable {\n return isFunction(input[Symbol_observable]);\n}\n", "import { isFunction } from './isFunction';\n\nexport function isAsyncIterable(obj: any): obj is AsyncIterable {\n return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]);\n}\n", "/**\n * Creates the TypeError to throw if an invalid object is passed to `from` or `scheduled`.\n * @param input The object that was passed.\n */\nexport function createInvalidObservableTypeError(input: any) {\n // TODO: We should create error codes that can be looked up, so this can be less verbose.\n return new TypeError(\n `You provided ${\n input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'`\n } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`\n );\n}\n", "export function getSymbolIterator(): symbol {\n if (typeof Symbol !== 'function' || !Symbol.iterator) {\n return '@@iterator' as any;\n }\n\n return Symbol.iterator;\n}\n\nexport const iterator = getSymbolIterator();\n", "import { iterator as Symbol_iterator } from '../symbol/iterator';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being an Iterable */\nexport function isIterable(input: any): input is Iterable {\n return isFunction(input?.[Symbol_iterator]);\n}\n", "import { ReadableStreamLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator {\n const reader = readableStream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n return;\n }\n yield value!;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport function isReadableStreamLike(obj: any): obj is ReadableStreamLike {\n // We don't want to use instanceof checks because they would return\n // false for instances from another Realm, like an