node-red-contrib-power-saver 3.4.3 → 3.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/{firebase-hosting-merge.yml → publish-doc-on-merge.yml} +6 -6
- package/.github/workflows/publish-to-npm.yml +23 -0
- package/.github/workflows/test-on-pull-request.yaml +13 -0
- package/docs/.vuepress/components/BestSaveVerificator.vue +18 -7
- package/docs/.vuepress/components/DonateButtons.vue +0 -1
- package/docs/.vuepress/config.js +9 -58
- package/docs/.vuepress/navbar.js +43 -0
- package/docs/changelog/README.md +18 -0
- package/docs/examples/example-cascade-temperature-control.md +0 -13
- package/docs/examples/example-heat-capacitor.md +9 -20
- package/docs/examples/example-nordpool-current-state.md +0 -13
- package/docs/examples/example-nordpool-events-state.md +0 -13
- package/docs/examples/example-tibber-mqtt.md +0 -13
- package/docs/faq/best-save-viewer.md +1 -0
- package/docs/guide/README.md +20 -12
- package/docs/images/best-save-config.png +0 -0
- package/docs/images/lowest-price-config.png +0 -0
- package/docs/nodes/ps-strategy-best-save.md +67 -11
- package/docs/nodes/ps-strategy-lowest-price.md +69 -0
- package/package.json +9 -10
- package/src/elvia/elvia-add-tariff.html +1 -1
- package/src/handle-input.js +30 -10
- package/src/receive-price-functions.js +2 -0
- package/src/strategy-best-save.html +14 -0
- package/src/strategy-best-save.js +6 -2
- package/src/strategy-lowest-price.html +15 -2
- package/src/strategy-lowest-price.js +3 -1
- package/src/utils.js +3 -3
- package/test/data/lowest-price-result-cont.json +1 -0
- package/test/data/lowest-price-result-missing-end.json +1 -0
- package/test/data/lowest-price-result-split-allday.json +1 -0
- package/test/data/lowest-price-result-split-allday10.json +1 -0
- package/test/data/lowest-price-result-split.json +1 -0
- package/test/data/tibber-result-end-0-24h.json +1 -0
- package/test/data/tibber-result-end-0.json +1 -0
- package/.github/workflows/firebase-hosting-pull-request.yml +0 -17
- package/docs/.vuepress/dist/.nojekyll +0 -0
- package/docs/.vuepress/dist/404.html +0 -33
- package/docs/.vuepress/dist/Ukraine-heart-shape-flag.png +0 -0
- package/docs/.vuepress/dist/assets/css/835.styles.c5afb22b.css +0 -1
- package/docs/.vuepress/dist/assets/css/896.styles.21a80cb6.css +0 -1
- package/docs/.vuepress/dist/assets/css/styles.1c48cbd0.css +0 -10
- package/docs/.vuepress/dist/assets/img/add-tariff-flow.eb700d4f.png +0 -0
- package/docs/.vuepress/dist/assets/img/back-to-top.8b37f773.svg +0 -1
- package/docs/.vuepress/dist/assets/img/best-save-config.79a2f39a.png +0 -0
- package/docs/.vuepress/dist/assets/img/copy-payload-best-save.b9192985.png +0 -0
- package/docs/.vuepress/dist/assets/img/elvia-config-no-config.b4bb972c.png +0 -0
- package/docs/.vuepress/dist/assets/img/elvia-config-no-tariff.3f89aba8.png +0 -0
- package/docs/.vuepress/dist/assets/img/elvia-config-select-tariff.0f73fd56.png +0 -0
- package/docs/.vuepress/dist/assets/img/elvia-config-subscription-key.8be8ab8a.png +0 -0
- package/docs/.vuepress/dist/assets/img/elvia-flow.bae2a4d5.png +0 -0
- package/docs/.vuepress/dist/assets/img/example-flow-1.3ff3e23f.png +0 -0
- package/docs/.vuepress/dist/assets/img/example-flow-2.b653b58d.png +0 -0
- package/docs/.vuepress/dist/assets/img/heat-capacitor-temperatureVsPrice.6e74905b.png +0 -0
- package/docs/.vuepress/dist/assets/img/lowest-price-config.6d66a8c2.png +0 -0
- package/docs/.vuepress/dist/assets/img/migrate-best-save.f73420f6.png +0 -0
- package/docs/.vuepress/dist/assets/img/migrate-power-saver.aae13f9d.png +0 -0
- package/docs/.vuepress/dist/assets/img/next-schedule-entity.4406856a.png +0 -0
- package/docs/.vuepress/dist/assets/img/next-schedule-flow.413ad62b.png +0 -0
- package/docs/.vuepress/dist/assets/img/next-schedule-sensor.eb896bdd.png +0 -0
- package/docs/.vuepress/dist/assets/img/node-power-saver.51ff2e5d.png +0 -0
- package/docs/.vuepress/dist/assets/img/node-ps-elvia-add-tariff.94ea2b09.png +0 -0
- package/docs/.vuepress/dist/assets/img/node-ps-general-add-tariff.a3cf6f06.png +0 -0
- package/docs/.vuepress/dist/assets/img/node-ps-receive-price.76eaa418.png +0 -0
- package/docs/.vuepress/dist/assets/img/node-ps-strategy-best-save.392292d5.png +0 -0
- package/docs/.vuepress/dist/assets/img/node-ps-strategy-heat-capacitor-cascade-control.2e75ed9e.png +0 -0
- package/docs/.vuepress/dist/assets/img/node-ps-strategy-heat-capacitor-simple-flow-example.29d9bf59.png +0 -0
- package/docs/.vuepress/dist/assets/img/node-ps-strategy-lowest-price.3a4ad347.png +0 -0
- package/docs/.vuepress/dist/assets/img/oven-setpoint-calculation.5bda0eec.png +0 -0
- package/docs/.vuepress/dist/assets/img/overshoot-time.b3b5d70e.png +0 -0
- package/docs/.vuepress/dist/assets/img/power-saver-nordpool-current-state.bf14afde.png +0 -0
- package/docs/.vuepress/dist/assets/img/power-saver-nordpool-events-state.8c392507.png +0 -0
- package/docs/.vuepress/dist/assets/img/power-saver-tibber-mqtt.16891dd2.png +0 -0
- package/docs/.vuepress/dist/assets/js/229.5c5378fa.js +0 -1
- package/docs/.vuepress/dist/assets/js/331.872104cd.js +0 -1
- package/docs/.vuepress/dist/assets/js/405.f4edd94d.js +0 -2
- package/docs/.vuepress/dist/assets/js/405.f4edd94d.js.LICENSE.txt +0 -8
- package/docs/.vuepress/dist/assets/js/490.1e639e05.js +0 -1
- package/docs/.vuepress/dist/assets/js/491.bd938119.js +0 -1
- package/docs/.vuepress/dist/assets/js/555.d8963d84.js +0 -1
- package/docs/.vuepress/dist/assets/js/811.5f659592.js +0 -1
- package/docs/.vuepress/dist/assets/js/app.6c6c1409.js +0 -1
- package/docs/.vuepress/dist/assets/js/runtime~app.f6ac32d7.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-0607240a.0193a377.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-08683c60.52e94cb6.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-0aca7ba6.cac5d4b9.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-0b5e3c8c.18561f6e.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-1ad821fa.6697a349.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-1b3a0ab8.c6c4e19b.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-1e2b191e.07b8ab21.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-29504124.00be7399.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-30acb564.28af12af.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-3706649a.c76d575b.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-4637f9e4.d334c29a.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-4c28314d.8cbb0f9d.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-510ed0d4.c04bc2e4.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-5954bcb2.dff3fc67.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-5db8da3a.e5e6d7a6.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-61f728ca.81968036.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-677dfaed.c159b0f4.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-7446a652.8fc2c591.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-7c87f26e.8ed52391.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-84304104.f3f07ed3.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-8daa1a0e.ed84ca09.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-b4a42144.9a2a0c9f.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-e8c55052.b7d52fc6.js +0 -1
- package/docs/.vuepress/dist/assets/js/v-fffb8e28.d09ab959.js +0 -1
- package/docs/.vuepress/dist/changelog/index.html +0 -33
- package/docs/.vuepress/dist/contribute/index.html +0 -33
- package/docs/.vuepress/dist/euro.png +0 -0
- package/docs/.vuepress/dist/examples/example-cascade-temperature-control.html +0 -304
- package/docs/.vuepress/dist/examples/example-heat-capacitor.html +0 -247
- package/docs/.vuepress/dist/examples/example-next-schedule-entity.html +0 -43
- package/docs/.vuepress/dist/examples/example-nordpool-current-state.html +0 -206
- package/docs/.vuepress/dist/examples/example-nordpool-events-state.html +0 -191
- package/docs/.vuepress/dist/examples/example-tibber-mqtt.html +0 -199
- package/docs/.vuepress/dist/examples/index.html +0 -33
- package/docs/.vuepress/dist/faq/best-save-viewer.html +0 -33
- package/docs/.vuepress/dist/faq/index.html +0 -33
- package/docs/.vuepress/dist/guide/index.html +0 -70
- package/docs/.vuepress/dist/index.html +0 -33
- package/docs/.vuepress/dist/logo.png +0 -0
- package/docs/.vuepress/dist/nodes/index.html +0 -33
- package/docs/.vuepress/dist/nodes/old-power-saver-doc.html +0 -115
- package/docs/.vuepress/dist/nodes/power-saver.html +0 -33
- package/docs/.vuepress/dist/nodes/ps-elvia-add-tariff.html +0 -33
- package/docs/.vuepress/dist/nodes/ps-general-add-tariff.html +0 -33
- package/docs/.vuepress/dist/nodes/ps-receive-price.html +0 -98
- package/docs/.vuepress/dist/nodes/ps-strategy-best-save.html +0 -104
- package/docs/.vuepress/dist/nodes/ps-strategy-heat-capacitor.html +0 -260
- package/docs/.vuepress/dist/nodes/ps-strategy-lowest-price.html +0 -124
- package/docs/.vuepress/dist/nodes/strategy-input.html +0 -58
|
@@ -25,6 +25,7 @@ The node can work on a specific period from 1 to 24 hours during a 24 hour perio
|
|
|
25
25
|
| Send When Rescheduling | Check this to make sure on or off output is sent immediately after rescheduling. |
|
|
26
26
|
| If No Schedule, Send | What to do if there is no valid schedule any more (turn on or off). |
|
|
27
27
|
| Outside Period, Send | Select the value to send outside the selected period. |
|
|
28
|
+
| Context storage | Select context storage to save data to, if you want other than the default. |
|
|
28
29
|
|
|
29
30
|
If you want to use a period of 24 hours, set the From Time and To Time to the same value. The time you select is significant in the way that it decides which 24 hours that are considered when finding the hours with lowest price.
|
|
30
31
|
|
|
@@ -55,6 +56,7 @@ It is possible to change config dynamically by sending a config message to the n
|
|
|
55
56
|
```json
|
|
56
57
|
"payload": {
|
|
57
58
|
"config": {
|
|
59
|
+
"contextStorage": "file",
|
|
58
60
|
"fromTime" : 10,
|
|
59
61
|
"toTime" : 16,
|
|
60
62
|
"hoursOn" : 3,
|
|
@@ -131,6 +133,29 @@ This operation cannot be undone.
|
|
|
131
133
|
However, it is normally not a big loss, as you can just feed the node with new price data and start from scratch.
|
|
132
134
|
:::
|
|
133
135
|
|
|
136
|
+
#### replan
|
|
137
|
+
|
|
138
|
+
By sending this command, you can have the node read the last received prices from the context storage,
|
|
139
|
+
and make a plan based on those prices:
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
"payload": {
|
|
143
|
+
"commands": {
|
|
144
|
+
"replan": true,
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
If the context storage is `file` you can use this to create a new schedule after a restart,
|
|
150
|
+
instead of fetching prices again.
|
|
151
|
+
|
|
152
|
+
### Config saved in context
|
|
153
|
+
|
|
154
|
+
The nodes config is saved in the nodes context.
|
|
155
|
+
If dynamic config is sent as input, this replaces the saved config.
|
|
156
|
+
It is the config that is saved in context that is used when calculating.
|
|
157
|
+
When Node-RED starts or the flow is redeployed, the config defined in the node replaces the saved config and will be used when planning.
|
|
158
|
+
|
|
134
159
|
## Input
|
|
135
160
|
|
|
136
161
|
The input is the [common strategy input format](./strategy-input.md)
|
|
@@ -196,6 +221,7 @@ Example of output:
|
|
|
196
221
|
],
|
|
197
222
|
"source": "Tibber",
|
|
198
223
|
"config": {
|
|
224
|
+
"contextStorage": "default",
|
|
199
225
|
"fromTime": "04",
|
|
200
226
|
"toTime": "18",
|
|
201
227
|
"hoursOn": "06",
|
|
@@ -211,6 +237,49 @@ Example of output:
|
|
|
211
237
|
|
|
212
238
|
The `schedule` array shows every time the switch is turned on or off. The `hours` array shows values per hour containing the price (received as input), whether that hour is on or off, the start time of the hour and the amount per kWh that is saved on hours that are turned off, compared to the next hour that is on.
|
|
213
239
|
|
|
240
|
+
## Restarts and saved context
|
|
241
|
+
|
|
242
|
+
The config, last received prices and the last calculated schedule are saved to the nodes context.
|
|
243
|
+
This may be saved to memory, to file or to another destination based on how your Node-RED is configured.
|
|
244
|
+
If multiple context storages are defined, you can select which one to use in the nodes config.
|
|
245
|
+
If there is only one context storage defined, this is normally `memory`. In that case, data is not saved over restarts.
|
|
246
|
+
It is common to have two different context storages defined, `memory` and `file`, but there may be more.
|
|
247
|
+
It is also common to have a `default` context storage defined, and often this points to either `memory` or `file`.
|
|
248
|
+
However, the configuration can be different from this.
|
|
249
|
+
|
|
250
|
+
You can find this configuration in the `settings.js` file for Node-RED, usually in the node-red config folder.
|
|
251
|
+
In Home Assistant, this is normally `/config/node-red/settings.js`.
|
|
252
|
+
|
|
253
|
+
Here is an example of a configuration for the context storage:
|
|
254
|
+
|
|
255
|
+
```js
|
|
256
|
+
contextStorage: {
|
|
257
|
+
file: { module: "localfilesystem"},
|
|
258
|
+
default: { module: "memory" }
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
By default, this node saves context to the `default` context storage. In the example above, this is memory.
|
|
263
|
+
Then it is not preserved over a restart.
|
|
264
|
+
Please read the [Node-RED documentation](https://nodered.org/docs/user-guide/context) for more details about this.
|
|
265
|
+
|
|
266
|
+
The data that is saved is the config, the last used prices and the last calculated schedule.
|
|
267
|
+
|
|
268
|
+
When Node-RED restarts, the config is reset to what is defined in the node config, so by default,
|
|
269
|
+
nothing is read from the context storage after a restart. However, if you send a `replan` command to the
|
|
270
|
+
nodes input, a plan is recalculated, using the last received prices. One way to do this is to use an `inject` node,
|
|
271
|
+
and set `msg.payload` to the following JSON value:
|
|
272
|
+
|
|
273
|
+
```json
|
|
274
|
+
{
|
|
275
|
+
"commands": {
|
|
276
|
+
"replan": true
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
This is an alternative to fetching new prices and send as input.
|
|
282
|
+
|
|
214
283
|
## Tips & tricks
|
|
215
284
|
|
|
216
285
|
### Multiple nodes works together
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-power-saver",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.2",
|
|
4
4
|
"description": "A module for Node-RED that you can use to turn on and off a switch based on power prices",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -45,22 +45,21 @@
|
|
|
45
45
|
"url": "https://github.com/ottopaulsen/node-red-contrib-power-saver.git"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@vuepress/bundler-
|
|
49
|
-
"@vuepress/plugin-register-components": "^2.0.0-beta.
|
|
50
|
-
"@vuepress/
|
|
48
|
+
"@vuepress/bundler-vite": "^2.0.0-beta.36",
|
|
49
|
+
"@vuepress/plugin-register-components": "^2.0.0-beta.36",
|
|
50
|
+
"@vuepress/plugin-search": "^2.0.0-beta.38",
|
|
51
|
+
"@vuepress/utils": "^2.0.0-beta.35",
|
|
51
52
|
"expect": "^27.5.1",
|
|
52
53
|
"mocha": "^9.2.0",
|
|
53
|
-
"node-red": "^2.2.
|
|
54
|
+
"node-red": "^2.2.2",
|
|
54
55
|
"node-red-node-test-helper": "^0.2.7",
|
|
55
|
-
"
|
|
56
|
-
"vuepress": "^2.0.0-beta.
|
|
57
|
-
"vuepress-webpack": "^2.0.0-beta.35"
|
|
56
|
+
"sass-loader": "^12.6.0",
|
|
57
|
+
"vuepress": "^2.0.0-beta.36"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@vitejs/plugin-vue": "^2.2.0",
|
|
61
60
|
"floating-vue": "^2.0.0-beta.6",
|
|
62
61
|
"lodash.clonedeep": "^4.5.0",
|
|
63
|
-
"luxon": "^2.3.
|
|
62
|
+
"luxon": "^2.3.1",
|
|
64
63
|
"node-fetch": "^2.6.7"
|
|
65
64
|
}
|
|
66
65
|
}
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
</script>
|
|
68
68
|
|
|
69
69
|
<script type="text/markdown" data-help-name="ps-elvia-add-tariff">
|
|
70
|
-
# Elvia Add Tariff
|
|
70
|
+
# Elvia Add Tariff (deprecated)
|
|
71
71
|
|
|
72
72
|
A node to get the tariff from Elvia and add it to the price before it is sent to the power saver strategy nodes.
|
|
73
73
|
Use this node between the receive-price node and any of the strategy nodes.
|
package/src/handle-input.js
CHANGED
|
@@ -8,13 +8,29 @@ function handleStrategyInput(node, msg, doPlanning) {
|
|
|
8
8
|
if (!validateInput(node, msg)) {
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
|
+
if (msg.payload.commands && !anyLegalCommands(msg.payload.commands)) {
|
|
12
|
+
const message = "Illegal command";
|
|
13
|
+
node.warn(message);
|
|
14
|
+
node.status({ fill: "yellow", shape: "dot", text: message });
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
11
17
|
if (msg.payload.commands && msg.payload.commands.reset) {
|
|
12
18
|
node.warn("Resetting node context by command");
|
|
13
19
|
// Reset all saved data
|
|
14
|
-
node
|
|
20
|
+
node
|
|
21
|
+
.context()
|
|
22
|
+
.set(["lastPlan", "lastPriceData", "lastSource"], [undefined, undefined, undefined], node.contextStorage);
|
|
15
23
|
deleteSavedScheduleBefore(node, DateTime.now().plus({ days: 2 }), 100);
|
|
16
24
|
}
|
|
17
|
-
|
|
25
|
+
let { priceData, source } = getPriceData(node, msg);
|
|
26
|
+
if (!priceData) {
|
|
27
|
+
// Use last saved price data
|
|
28
|
+
priceData = node.context().get("lastPriceData");
|
|
29
|
+
source = node.context().get("lastSource");
|
|
30
|
+
const message = "Using saved prices";
|
|
31
|
+
node.warn(message);
|
|
32
|
+
node.status({ fill: "green", shape: "ring", text: message });
|
|
33
|
+
}
|
|
18
34
|
if (!priceData) {
|
|
19
35
|
const message = "No price data";
|
|
20
36
|
node.warn(message);
|
|
@@ -38,7 +54,7 @@ function handleStrategyInput(node, msg, doPlanning) {
|
|
|
38
54
|
const plan = doPlanning(node, effectiveConfig, priceData, planFromTime, dateDayBefore, dateToday);
|
|
39
55
|
|
|
40
56
|
// Save schedule
|
|
41
|
-
node.context().set("lastPlan", plan);
|
|
57
|
+
node.context().set("lastPlan", plan, node.contextStorage);
|
|
42
58
|
dates.forEach((d) => saveDayData(node, d, extractPlanForDate(plan, d)));
|
|
43
59
|
|
|
44
60
|
const sentOnCommand = !!msg.payload.commands?.sendSchedule;
|
|
@@ -84,14 +100,14 @@ function getPriceData(node, msg) {
|
|
|
84
100
|
const isCommandMsg = !!msg?.payload?.commands;
|
|
85
101
|
const isPriceMsg = !!msg?.payload?.priceData;
|
|
86
102
|
if ((isConfigMsg || isCommandMsg) && !isPriceMsg) {
|
|
87
|
-
const priceData = node.context().get("lastPriceData");
|
|
88
|
-
const source = node.context().get("lastSource");
|
|
103
|
+
const priceData = node.context().get("lastPriceData", node.contextStorage);
|
|
104
|
+
const source = node.context().get("lastSource", node.contextStorage);
|
|
89
105
|
return { priceData, source };
|
|
90
106
|
}
|
|
91
107
|
const priceData = msg.payload.priceData;
|
|
92
108
|
const source = msg.payload.source;
|
|
93
|
-
node.context().set("lastPriceData", priceData);
|
|
94
|
-
node.context().set("lastSource", source);
|
|
109
|
+
node.context().set("lastPriceData", priceData, node.contextStorage);
|
|
110
|
+
node.context().set("lastSource", source, node.contextStorage);
|
|
95
111
|
return { priceData, source };
|
|
96
112
|
}
|
|
97
113
|
|
|
@@ -129,14 +145,14 @@ function deleteSavedScheduleBefore(node, day, checkDays = 0) {
|
|
|
129
145
|
let count = 0;
|
|
130
146
|
do {
|
|
131
147
|
date = date.plus({ days: -1 });
|
|
132
|
-
data = node.context().get(date.toISODate());
|
|
133
|
-
node.context().set(date.toISODate(), undefined);
|
|
148
|
+
data = node.context().get(date.toISODate(), node.contextStorage);
|
|
149
|
+
node.context().set(date.toISODate(), undefined, node.contextStorage);
|
|
134
150
|
count++;
|
|
135
151
|
} while (data !== undefined || count <= checkDays);
|
|
136
152
|
}
|
|
137
153
|
|
|
138
154
|
function saveDayData(node, date, plan) {
|
|
139
|
-
node.context().set(date, plan);
|
|
155
|
+
node.context().set(date, plan, node.contextStorage);
|
|
140
156
|
}
|
|
141
157
|
|
|
142
158
|
function sendSwitch(node, onOff) {
|
|
@@ -181,6 +197,10 @@ function validateInput(node, msg) {
|
|
|
181
197
|
return true;
|
|
182
198
|
}
|
|
183
199
|
|
|
200
|
+
function anyLegalCommands(commands) {
|
|
201
|
+
return ["reset", "replan", "sendOutput", "sendSchedule"].some((v) => commands.hasOwnProperty(v));
|
|
202
|
+
}
|
|
203
|
+
|
|
184
204
|
module.exports = {
|
|
185
205
|
handleStrategyInput,
|
|
186
206
|
validateInput,
|
|
@@ -14,6 +14,8 @@ function getPriceData(node, msg) {
|
|
|
14
14
|
priceData = [...input.today, ...input.tomorrow];
|
|
15
15
|
const source = input.source;
|
|
16
16
|
node.context().set("lastPriceData", priceData);
|
|
17
|
+
const statusMsg = priceData.length + " hours from " + source;
|
|
18
|
+
node.status({ fill: "green", shape: "ring", text: statusMsg });
|
|
17
19
|
return { priceData, source };
|
|
18
20
|
}
|
|
19
21
|
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
align: "left",
|
|
27
27
|
},
|
|
28
28
|
outputIfNoSchedule: { value: "true", required: true, align: "left" },
|
|
29
|
+
contextStorage: { value: "default", required: false, align: "left" },
|
|
29
30
|
},
|
|
30
31
|
inputs: 1,
|
|
31
32
|
outputs: 3,
|
|
@@ -47,6 +48,14 @@
|
|
|
47
48
|
},
|
|
48
49
|
],
|
|
49
50
|
});
|
|
51
|
+
$("#node-input-contextStorage").typedInput({
|
|
52
|
+
types: [
|
|
53
|
+
{
|
|
54
|
+
value: "storages",
|
|
55
|
+
options: RED.settings.context.stores.map((s) => ({ value: s, label: s })),
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
});
|
|
50
59
|
},
|
|
51
60
|
});
|
|
52
61
|
</script>
|
|
@@ -86,6 +95,11 @@
|
|
|
86
95
|
<input type="text" id="node-input-outputIfNoSchedule" style="width: 80px">
|
|
87
96
|
</label>
|
|
88
97
|
</div>
|
|
98
|
+
<h3>Context storage</h3>
|
|
99
|
+
<div class="form-row">
|
|
100
|
+
<label for="node-input-contextStorage"><i class="fa fa-archive"></i> Context storage</label>
|
|
101
|
+
<input type="text" id="node-input-contextStorage" style="width: 160px">
|
|
102
|
+
</div>
|
|
89
103
|
</script>
|
|
90
104
|
|
|
91
105
|
<script type="text/markdown" data-help-name="ps-strategy-best-save">
|
|
@@ -9,14 +9,18 @@ module.exports = function (RED) {
|
|
|
9
9
|
RED.nodes.createNode(this, config);
|
|
10
10
|
const node = this;
|
|
11
11
|
|
|
12
|
+
node.status({});
|
|
13
|
+
|
|
12
14
|
const originalConfig = {
|
|
13
15
|
maxHoursToSaveInSequence: config.maxHoursToSaveInSequence,
|
|
14
16
|
minHoursOnAfterMaxSequenceSaved: config.minHoursOnAfterMaxSequenceSaved,
|
|
15
17
|
minSaving: parseFloat(config.minSaving),
|
|
16
18
|
sendCurrentValueWhenRescheduling: config.sendCurrentValueWhenRescheduling,
|
|
17
19
|
outputIfNoSchedule: config.outputIfNoSchedule === "true",
|
|
20
|
+
contextStorage: config.contextStorage || "default",
|
|
18
21
|
};
|
|
19
|
-
node.context().set("config", originalConfig);
|
|
22
|
+
node.context().set("config", originalConfig, originalConfig.contextStorage);
|
|
23
|
+
node.contextStorage = originalConfig.contextStorage;
|
|
20
24
|
|
|
21
25
|
node.on("close", function () {
|
|
22
26
|
clearTimeout(node.schedulingTimeout);
|
|
@@ -55,7 +59,7 @@ function doPlanning(node, _, priceData, _, dateDayBefore, _) {
|
|
|
55
59
|
const values = priceData.map((d) => d.value);
|
|
56
60
|
const startTimes = priceData.map((d) => d.start);
|
|
57
61
|
const onOffBefore = dataJustBefore.hours.map((h) => h.onOff);
|
|
58
|
-
const lastPlanHours = node.context().get("lastPlan")?.hours ?? [];
|
|
62
|
+
const lastPlanHours = node.context().get("lastPlan", node.contextStorage)?.hours ?? [];
|
|
59
63
|
const plan = makePlan(node, values, startTimes, onOffBefore);
|
|
60
64
|
const includeFromLastPlanHours = lastPlanHours.filter(
|
|
61
65
|
(h) => h.start < plan.hours[0].start && h.start >= priceData[0].start
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
outputIfNoSchedule: { value: "true", required: true, align: "left" },
|
|
57
57
|
outputOutsidePeriod: { value: "false", required: true, align: "left" },
|
|
58
|
+
contextStorage: { value: "default", required: false, align: "left" },
|
|
58
59
|
},
|
|
59
60
|
inputs: 1,
|
|
60
61
|
outputs: 3,
|
|
@@ -115,6 +116,14 @@
|
|
|
115
116
|
},
|
|
116
117
|
],
|
|
117
118
|
});
|
|
119
|
+
$("#node-input-contextStorage").typedInput({
|
|
120
|
+
types: [
|
|
121
|
+
{
|
|
122
|
+
value: "storages",
|
|
123
|
+
options: RED.settings.context.stores.map((s) => ({ value: s, label: s })),
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
});
|
|
118
127
|
},
|
|
119
128
|
});
|
|
120
129
|
</script>
|
|
@@ -125,11 +134,11 @@
|
|
|
125
134
|
<input type="text" id="node-input-name" placeholder="Name" style="width: 240px">
|
|
126
135
|
</div>
|
|
127
136
|
<div class="form-row">
|
|
128
|
-
<label for="node-input-fromTime"><i class="fa fa-
|
|
137
|
+
<label for="node-input-fromTime"><i class="fa fa-clock-o"></i> From time</label>
|
|
129
138
|
<input type="text" id="node-input-fromTime" style="width: 80px">
|
|
130
139
|
</div>
|
|
131
140
|
<div class="form-row">
|
|
132
|
-
<label for="node-input-toTime"><i class="fa fa-
|
|
141
|
+
<label for="node-input-toTime"><i class="fa fa-clock-o"></i> To time</label>
|
|
133
142
|
<input type="text" id="node-input-toTime" style="width: 80px">
|
|
134
143
|
</div>
|
|
135
144
|
<div class="form-row">
|
|
@@ -159,6 +168,10 @@
|
|
|
159
168
|
<input type="text" id="node-input-outputOutsidePeriod" style="width: 80px">
|
|
160
169
|
</label>
|
|
161
170
|
</div>
|
|
171
|
+
<div class="form-row">
|
|
172
|
+
<label for="node-input-contextStorage"><i class="fa fa-archive"></i> Context storage</label>
|
|
173
|
+
<input type="text" id="node-input-contextStorage" style="width: 160px">
|
|
174
|
+
</div>
|
|
162
175
|
</script>
|
|
163
176
|
|
|
164
177
|
<script type="text/markdown" data-help-name="ps-strategy-lowest-price">
|
|
@@ -7,6 +7,7 @@ module.exports = function (RED) {
|
|
|
7
7
|
function StrategyLowestPriceNode(config) {
|
|
8
8
|
RED.nodes.createNode(this, config);
|
|
9
9
|
const node = this;
|
|
10
|
+
node.status({});
|
|
10
11
|
|
|
11
12
|
const originalConfig = {
|
|
12
13
|
fromTime: config.fromTime,
|
|
@@ -16,8 +17,9 @@ module.exports = function (RED) {
|
|
|
16
17
|
sendCurrentValueWhenRescheduling: booleanConfig(config.sendCurrentValueWhenRescheduling),
|
|
17
18
|
outputIfNoSchedule: booleanConfig(config.outputIfNoSchedule),
|
|
18
19
|
outputOutsidePeriod: booleanConfig(config.outputOutsidePeriod),
|
|
20
|
+
contextStorage: config.contextStorage || "default",
|
|
19
21
|
};
|
|
20
|
-
node.context().set("config", originalConfig);
|
|
22
|
+
node.context().set("config", originalConfig, node.contextStorage);
|
|
21
23
|
|
|
22
24
|
node.on("close", function () {
|
|
23
25
|
clearTimeout(node.schedulingTimeout);
|
package/src/utils.js
CHANGED
|
@@ -57,14 +57,14 @@ function getDiff(large, small) {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
function getEffectiveConfig(node, msg) {
|
|
60
|
-
const res = node.context().get("config");
|
|
60
|
+
const res = node.context().get("config", node.contextStorage);
|
|
61
61
|
const isConfigMsg = !!msg?.payload?.config;
|
|
62
62
|
if (isConfigMsg) {
|
|
63
63
|
const inputConfig = msg.payload.config;
|
|
64
64
|
Object.keys(inputConfig).forEach((key) => {
|
|
65
65
|
res[key] = inputConfig[key];
|
|
66
66
|
});
|
|
67
|
-
node.context().set("config", res);
|
|
67
|
+
node.context().set("config", res, node.contextStorage);
|
|
68
68
|
}
|
|
69
69
|
return res;
|
|
70
70
|
}
|
|
@@ -73,7 +73,7 @@ function loadDayData(node, date) {
|
|
|
73
73
|
// Load saved schedule for the date (YYYY-MM-DD)
|
|
74
74
|
// Return null if not found
|
|
75
75
|
const key = date.toISODate();
|
|
76
|
-
const saved = node.context().get(key);
|
|
76
|
+
const saved = node.context().get(key, node.contextStorage);
|
|
77
77
|
const res = saved ?? {
|
|
78
78
|
schedule: [],
|
|
79
79
|
hours: [],
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# This file was auto-generated by the Firebase CLI
|
|
2
|
-
# https://github.com/firebase/firebase-tools
|
|
3
|
-
|
|
4
|
-
name: Deploy to Firebase Hosting on PR
|
|
5
|
-
'on': pull_request
|
|
6
|
-
jobs:
|
|
7
|
-
build_and_preview:
|
|
8
|
-
if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}'
|
|
9
|
-
runs-on: ubuntu-latest
|
|
10
|
-
steps:
|
|
11
|
-
- uses: actions/checkout@v2
|
|
12
|
-
- run: 'npm run docs:build'
|
|
13
|
-
- uses: FirebaseExtended/action-hosting-deploy@v0
|
|
14
|
-
with:
|
|
15
|
-
repoToken: '${{ secrets.GITHUB_TOKEN }}'
|
|
16
|
-
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_POWERSAVER_C98D0 }}'
|
|
17
|
-
projectId: powersaver-c98d0
|
|
File without changes
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en-US">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
-
<meta name="generator" content="VuePress 2.0.0-beta.35">
|
|
7
|
-
<style>
|
|
8
|
-
:root {
|
|
9
|
-
--c-bg: #fff;
|
|
10
|
-
}
|
|
11
|
-
html.dark {
|
|
12
|
-
--c-bg: #22272e;
|
|
13
|
-
}
|
|
14
|
-
html, body {
|
|
15
|
-
background-color: var(--c-bg);
|
|
16
|
-
}
|
|
17
|
-
</style>
|
|
18
|
-
<script>
|
|
19
|
-
const userMode = localStorage.getItem('vuepress-color-scheme');
|
|
20
|
-
const systemDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
21
|
-
if (userMode === 'dark' || (userMode !== 'light' && systemDarkMode)) {
|
|
22
|
-
document.documentElement.classList.toggle('dark', true);
|
|
23
|
-
}
|
|
24
|
-
</script>
|
|
25
|
-
<link rel="shortcut icon" type="image/x-icon" href="euro.png"><script async src="https://www.googletagmanager.com/gtag/js?id=G-Z2QNNCDQZG"></script><script src="window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-Z2QNNCDQZG');"></script><title>Power Saver</title><meta name="description" content="A Node-RED note to save money on hourly changing power prices">
|
|
26
|
-
<link rel="preload" href="/assets/js/runtime~app.f6ac32d7.js" as="script"><link rel="preload" href="/assets/css/styles.1c48cbd0.css" as="style"><link rel="preload" href="/assets/js/405.f4edd94d.js" as="script"><link rel="preload" href="/assets/js/app.6c6c1409.js" as="script">
|
|
27
|
-
<link rel="stylesheet" href="/assets/css/styles.1c48cbd0.css">
|
|
28
|
-
</head>
|
|
29
|
-
<body>
|
|
30
|
-
<div id="app"><!--[--><div class="theme-container"><div class="theme-default-content"><h1>404</h1><blockquote>There's nothing here.</blockquote><a href="/" class="">Take me home</a></div></div><!----><!--]--></div>
|
|
31
|
-
<script src="/assets/js/runtime~app.f6ac32d7.js" defer></script><script src="/assets/js/405.f4edd94d.js" defer></script><script src="/assets/js/app.6c6c1409.js" defer></script>
|
|
32
|
-
</body>
|
|
33
|
-
</html>
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.box[data-v-66d8d572]{display:flex;justify-content:space-evenly}left[data-v-66d8d572],right[data-v-66d8d572]{flex:1 1 auto}.buttonDiv[data-v-66d8d572],.patreonButton[data-v-66d8d572]{-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;font-family:aktiv-grotesk,sans-serif;-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility}.buttonDiv[data-v-66d8d572]{color:#241e12;font-size:16px;width:11rem}.patreonButton[data-v-66d8d572]{align-items:center;-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:#ff424d;border-radius:9999px;border:1px solid #ff424d;box-sizing:border-box;display:inline-flex;font-weight:500;height:unset;justify-content:center;padding:.46875rem 1rem;position:relative;pointer-events:unset;text-align:center;text-transform:none;transition:all 300ms cubic-bezier(.19,1,.22,1) 0s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;white-space:unset;width:100%;color:#fff!important;font-size:.875rem!important;text-decoration:none;cursor:pointer}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.config>p[data-v-a3dd0286]{margin:0;padding:0}td[data-v-a3dd0286],th[data-v-a3dd0286]{margin:2px 4px;padding:2px 4px;font-size:small}.highlightSequence[data-v-a3dd0286]{background-color:#87cefa}.highlightSaving[data-v-a3dd0286]{background-color:#ff0}.highlightBoth[data-v-a3dd0286]{background-color:#90ee90}.belowMin[data-v-a3dd0286]{color:red}.selectable[data-v-a3dd0286]{cursor:pointer}.sepcol[data-v-a3dd0286]{background-color:#a9a9a9}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
:root{--external-link-icon-color:#aaa}
|
|
2
|
-
.external-link-icon{position:relative;display:inline-block;color:var(--external-link-icon-color);vertical-align:middle;top:-1px}.external-link-icon-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
|
3
|
-
:root{--medium-zoom-z-index:100;--medium-zoom-bg-color:#ffffff;--medium-zoom-opacity:1}
|
|
4
|
-
.medium-zoom-overlay{background-color:var(--medium-zoom-bg-color)!important;z-index:var(--medium-zoom-z-index)}.medium-zoom-overlay~img{z-index:calc(var(--medium-zoom-z-index) + 1)}.medium-zoom--opened .medium-zoom-overlay{opacity:var(--medium-zoom-opacity)}
|
|
5
|
-
:root{--c-brand:#3eaf7c;--c-brand-light:#4abf8a;--c-bg:#ffffff;--c-bg-light:#f3f4f5;--c-bg-lighter:#eeeeee;--c-bg-navbar:var(--c-bg);--c-bg-sidebar:var(--c-bg);--c-bg-arrow:#cccccc;--c-text:#2c3e50;--c-text-accent:var(--c-brand);--c-text-light:#3a5169;--c-text-lighter:#4e6e8e;--c-text-lightest:#6a8bad;--c-text-quote:#999999;--c-border:#eaecef;--c-border-dark:#dfe2e5;--c-tip:#42b983;--c-tip-bg:var(--c-bg-light);--c-tip-title:var(--c-text);--c-tip-text:var(--c-text);--c-tip-text-accent:var(--c-text-accent);--c-warning:#e7c000;--c-warning-bg:#fffae3;--c-warning-title:#ad9000;--c-warning-text:#746000;--c-warning-text-accent:var(--c-text);--c-danger:#cc0000;--c-danger-bg:#ffe0e0;--c-danger-title:#990000;--c-danger-text:#660000;--c-danger-text-accent:var(--c-text);--c-details-bg:#eeeeee;--c-badge-tip:var(--c-tip);--c-badge-warning:var(--c-warning);--c-badge-danger:var(--c-danger);--t-color:0.3s ease;--t-transform:0.3s ease;--code-bg-color:#282c34;--code-hl-bg-color:rgba(0, 0, 0, 0.66);--code-ln-color:#9e9e9e;--code-ln-wrapper-width:3.5rem;--font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;--font-family-code:Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;--navbar-height:3.6rem;--navbar-padding-v:0.7rem;--navbar-padding-h:1.5rem;--sidebar-width:20rem;--sidebar-width-mobile:calc(var(--sidebar-width) * 0.82);--content-width:740px;--homepage-width:960px}.back-to-top{--back-to-top-color:var(--c-brand);--back-to-top-color-hover:var(--c-brand-light)}.DocSearch{--docsearch-primary-color:var(--c-brand);--docsearch-text-color:var(--c-text);--docsearch-highlight-color:var(--c-brand);--docsearch-muted-color:var(--c-text-quote);--docsearch-container-background:rgba(9, 10, 17, 0.8);--docsearch-modal-background:var(--c-bg-light);--docsearch-searchbox-background:var(--c-bg-lighter);--docsearch-searchbox-focus-background:var(--c-bg);--docsearch-searchbox-shadow:inset 0 0 0 2px var(--c-brand);--docsearch-hit-color:var(--c-text-light);--docsearch-hit-active-color:var(--c-bg);--docsearch-hit-background:var(--c-bg);--docsearch-hit-shadow:0 1px 3px 0 var(--c-border-dark);--docsearch-footer-background:var(--c-bg)}.external-link-icon{--external-link-icon-color:var(--c-text-quote)}.medium-zoom-overlay{--medium-zoom-bg-color:var(--c-bg)}#nprogress{--nprogress-color:var(--c-brand)}.pwa-popup{--pwa-popup-text-color:var(--c-text);--pwa-popup-bg-color:var(--c-bg);--pwa-popup-border-color:var(--c-brand);--pwa-popup-shadow:0 4px 16px var(--c-brand);--pwa-popup-btn-text-color:var(--c-bg);--pwa-popup-btn-bg-color:var(--c-brand);--pwa-popup-btn-hover-bg-color:var(--c-brand-light)}.search-box{--search-bg-color:var(--c-bg);--search-accent-color:var(--c-brand);--search-text-color:var(--c-text);--search-border-color:var(--c-border);--search-item-text-color:var(--c-text-lighter);--search-item-focus-bg-color:var(--c-bg-light)}html.dark{--c-brand:#3aa675;--c-brand-light:#349469;--c-bg:#22272e;--c-bg-light:#2b313a;--c-bg-lighter:#262c34;--c-text:#adbac7;--c-text-light:#96a7b7;--c-text-lighter:#8b9eb0;--c-text-lightest:#8094a8;--c-border:#3e4c5a;--c-border-dark:#34404c;--c-tip:#318a62;--c-warning:#ceab00;--c-warning-bg:#7e755b;--c-warning-title:#ceac03;--c-warning-text:#362e00;--c-danger:#940000;--c-danger-bg:#806161;--c-danger-title:#610000;--c-danger-text:#3a0000;--c-details-bg:#323843;--code-hl-bg-color:#363b46;color-scheme:dark}html.dark .DocSearch{--docsearch-logo-color:var(--c-text);--docsearch-modal-shadow:inset 1px 1px 0 0 #2c2e40, 0 3px 8px 0 #000309;--docsearch-key-shadow:inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px #51577d, 0 2px 2px 0 rgba(3, 4, 9, 0.3);--docsearch-key-gradient:linear-gradient(-225deg, #444950, #1c1e21);--docsearch-footer-shadow:inset 0 1px 0 0 rgba(73, 76, 106, 0.5), 0 -4px 8px 0 rgba(0, 0, 0, 0.2)}body,html{padding:0;margin:0;background-color:var(--c-bg);transition:background-color var(--t-color)}body{font-family:var(--font-family);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-size:16px}a,p a code{color:var(--c-text-accent)}a{font-weight:500;text-decoration:none;overflow-wrap:break-word}p a code{font-weight:400}code,kbd{font-family:var(--font-family-code)}body,kbd{color:var(--c-text)}kbd{background:var(--c-bg-lighter);border:solid .15rem var(--c-border-dark);border-bottom:solid .25rem var(--c-border-dark);border-radius:.15rem;padding:0 .15em}code{color:var(--c-text-lighter);padding:.25rem .5rem;font-size:.85em;background-color:var(--c-bg-light);border-radius:3px;overflow-wrap:break-word;transition:background-color var(--t-color)}blockquote{font-size:1rem;color:var(--c-text-quote);border-left:.2rem solid var(--c-border-dark);margin:1rem 0;padding:.25rem 0 .25rem 1rem}blockquote>p,code{margin:0}ol,ul{padding-left:1.2em}strong{font-weight:600}h1,h2,h3,h4,h5,h6{font-weight:600;line-height:1.25}h1:focus-visible,h2:focus-visible,h3:focus-visible,h4:focus-visible,h5:focus-visible,h6:focus-visible{outline:0}h1:hover .header-anchor,h2:hover .header-anchor,h3:hover .header-anchor,h4:hover .header-anchor,h5:hover .header-anchor,h6:hover .header-anchor{opacity:1}h1{font-size:2.2rem}h2{font-size:1.65rem;padding-bottom:.3rem;border-bottom:1px solid var(--c-border);transition:border-color var(--t-color)}h3{font-size:1.35rem}h4{font-size:1.15rem}h5{font-size:1.05rem}h6{font-size:1rem}a.header-anchor{font-size:.85em;float:left;margin-left:-.87em;padding-right:.23em;margin-top:.125em;opacity:0}a.header-anchor:hover{text-decoration:none}a.header-anchor:focus-visible{opacity:1}ol,p,ul{line-height:1.7}hr{border:0;border-top:1px solid var(--c-border)}table,tr{transition:border-color var(--t-color)}table{border-collapse:collapse;margin:1rem 0;display:block;overflow-x:auto}tr{border-top:1px solid var(--c-border-dark)}tr:nth-child(2n){background-color:var(--c-bg-light);transition:background-color var(--t-color)}td,th{padding:.6em 1em;border:1px solid var(--c-border-dark);transition:border-color var(--t-color)}.arrow,.badge{display:inline-block}.arrow{width:0;height:0}.arrow.down,.arrow.up{border-left:4px solid transparent;border-right:4px solid transparent}.arrow.up{border-bottom:6px solid var(--c-bg-arrow)}.arrow.down{border-top:6px solid var(--c-bg-arrow)}.arrow.left,.arrow.right{border-top:4px solid transparent;border-bottom:4px solid transparent}.arrow.right{border-left:6px solid var(--c-bg-arrow)}.arrow.left{border-right:6px solid var(--c-bg-arrow)}.badge{font-size:14px;height:18px;line-height:18px;border-radius:3px;padding:0 6px;color:var(--c-bg);vertical-align:top;transition:color var(--t-color),background-color var(--t-color)}.badge.tip{background-color:var(--c-badge-tip)}.badge.warning{background-color:var(--c-badge-warning)}.badge.danger{background-color:var(--c-badge-danger)}.badge+.badge{margin-left:5px}code[class*=language-],pre[class*=language-]{color:#ccc;background:0 0;font-family:var(--font-family-code);font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#ec5975}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:#3eaf7c}.theme-default-content pre,.theme-default-content pre[class*=language-]{line-height:1.4;padding:1.3rem 1.5rem;margin:.85rem 0;border-radius:6px;overflow:auto}.theme-default-content pre code,.theme-default-content pre[class*=language-] code{color:#fff;padding:0;background-color:transparent;border-radius:0;overflow-wrap:unset;-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}.theme-default-content .line-number{font-family:var(--font-family-code)}div[class*=language-]{position:relative;background-color:var(--code-bg-color);border-radius:6px}div[class*=language-]::before{position:absolute;z-index:3;top:.8em;right:1em;font-size:.75rem;color:var(--code-ln-color)}div[class*=language-] pre,div[class*=language-] pre[class*=language-]{background:0 0!important;position:relative;z-index:1}div[class*=language-] .highlight-lines{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding-top:1.3rem;position:absolute;top:0;left:0;width:100%;line-height:1.4}div[class*=language-] .highlight-lines .highlight-line{background-color:var(--code-hl-bg-color)}div[class*=language-]:not(.line-numbers-mode) .line-numbers{display:none}div[class*=language-].line-numbers-mode .highlight-lines .highlight-line{position:relative}div[class*=language-].line-numbers-mode .highlight-lines .highlight-line::before{content:" ";position:absolute;z-index:2;left:0;top:0;display:block;width:var(--code-ln-wrapper-width);height:100%}div[class*=language-].line-numbers-mode pre{margin-left:var(--code-ln-wrapper-width);padding-left:1rem;vertical-align:middle}div[class*=language-].line-numbers-mode .line-numbers{position:absolute;top:0;width:var(--code-ln-wrapper-width);text-align:center;color:var(--code-ln-color);padding-top:1.25rem;line-height:1.4}div[class*=language-].line-numbers-mode .line-numbers .line-number,div[class*=language-].line-numbers-mode .line-numbers br{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div[class*=language-].line-numbers-mode .line-numbers .line-number{position:relative;z-index:3;font-size:.85em;line-height:0}div[class*=language-].line-numbers-mode::after{content:"";position:absolute;top:0;left:0;width:var(--code-ln-wrapper-width);height:100%;border-radius:6px 0 0 6px;border-right:1px solid var(--code-hl-bg-color)}div[class*=language-].ext-c:before{content:"c"}div[class*=language-].ext-cpp:before{content:"cpp"}div[class*=language-].ext-cs:before{content:"cs"}div[class*=language-].ext-css:before{content:"css"}div[class*=language-].ext-dart:before{content:"dart"}div[class*=language-].ext-docker:before{content:"docker"}div[class*=language-].ext-fs:before{content:"fs"}div[class*=language-].ext-go:before{content:"go"}div[class*=language-].ext-html:before{content:"html"}div[class*=language-].ext-java:before{content:"java"}div[class*=language-].ext-js:before{content:"js"}div[class*=language-].ext-json:before{content:"json"}div[class*=language-].ext-kt:before{content:"kt"}div[class*=language-].ext-less:before{content:"less"}div[class*=language-].ext-makefile:before{content:"makefile"}div[class*=language-].ext-md:before{content:"md"}div[class*=language-].ext-php:before{content:"php"}div[class*=language-].ext-py:before{content:"py"}div[class*=language-].ext-rb:before{content:"rb"}div[class*=language-].ext-rs:before{content:"rs"}div[class*=language-].ext-sass:before{content:"sass"}div[class*=language-].ext-scss:before{content:"scss"}div[class*=language-].ext-sh:before{content:"sh"}div[class*=language-].ext-styl:before{content:"styl"}div[class*=language-].ext-ts:before{content:"ts"}div[class*=language-].ext-toml:before{content:"toml"}div[class*=language-].ext-vue:before{content:"vue"}div[class*=language-].ext-yml:before{content:"yml"}@media (max-width:419px){.theme-default-content div[class*=language-]{margin:.85rem -1.5rem;border-radius:0}}.code-group__nav{margin-top:.85rem;margin-bottom:calc(-1.7rem - 6px);padding-bottom:calc(1.7rem - 6px);padding-left:10px;padding-top:10px;border-top-left-radius:6px;border-top-right-radius:6px;background-color:var(--code-bg-color)}.code-group__ul{margin:auto 0;padding-left:0;display:inline-flex;list-style:none}.code-group__nav-tab{border:0;padding:5px;cursor:pointer;background-color:transparent;font-size:.85em;line-height:1.4;color:rgba(255,255,255,.9);font-weight:600}.code-group__nav-tab:focus{outline:0}.code-group__nav-tab:focus-visible{outline:1px solid rgba(255,255,255,.9)}.code-group__nav-tab-active{border-bottom:var(--c-brand) 1px solid}@media (max-width:419px){.code-group__nav{margin-left:-1.5rem;margin-right:-1.5rem;border-radius:0}}.code-group-item,.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subtitle>a.router-link-active::after{display:none}.code-group-item__active{display:block}.code-group-item>pre{background-color:orange}.custom-container{transition:color var(--t-color),border-color var(--t-color),background-color var(--t-color)}.custom-container .custom-container-title{font-weight:600;margin-bottom:-.4rem}.custom-container.danger,.custom-container.tip,.custom-container.warning{padding:.1rem 1.5rem;border-left-width:.5rem;border-left-style:solid;margin:1rem 0}.custom-container.tip{border-color:var(--c-tip);background-color:var(--c-tip-bg);color:var(--c-tip-text)}.custom-container.tip .custom-container-title{color:var(--c-tip-title)}.custom-container.tip a{color:var(--c-tip-text-accent)}.custom-container.warning{border-color:var(--c-warning);background-color:var(--c-warning-bg);color:var(--c-warning-text)}.custom-container.warning .custom-container-title{color:var(--c-warning-title)}.custom-container.warning a{color:var(--c-warning-text-accent)}.custom-container.danger{border-color:var(--c-danger);background-color:var(--c-danger-bg);color:var(--c-danger-text)}.custom-container.danger .custom-container-title{color:var(--c-danger-title)}.custom-container.danger a{color:var(--c-danger-text-accent)}.custom-container.details{display:block;position:relative;border-radius:2px;margin:1.6em 0;padding:1.6em;background-color:var(--c-details-bg)}.custom-container.details h4{margin-top:0}.custom-container.details figure:last-child,.custom-container.details p:last-child{margin-bottom:0;padding-bottom:0}.custom-container.details summary{outline:0;cursor:pointer}.home{padding:var(--navbar-height) 2rem 0;max-width:var(--homepage-width);margin:0 auto;display:block}.home .hero{text-align:center}.home .hero img{max-width:100%;max-height:280px;display:block;margin:3rem auto 1.5rem}.home .hero h1{font-size:3rem}.home .hero .actions,.home .hero .description,.home .hero h1{margin:1.8rem auto}.home .hero .actions{display:flex;flex-wrap:wrap;gap:1rem;justify-content:center}.home .hero .description{max-width:35rem;font-size:1.6rem;line-height:1.3;color:var(--c-text-lightest)}.home .hero .action-button{display:inline-block;font-size:1.2rem;padding:.8rem 1.6rem;border-width:2px;border-style:solid;border-radius:4px;transition:background-color var(--t-color);box-sizing:border-box}.home .hero .action-button.primary{color:var(--c-bg);background-color:var(--c-brand);border-color:var(--c-brand)}.home .hero .action-button.primary:hover{background-color:var(--c-brand-light)}.home .hero .action-button.secondary{color:var(--c-brand);background-color:var(--c-bg);border-color:var(--c-brand)}.home .hero .action-button.secondary:hover{color:var(--c-bg);background-color:var(--c-brand-light)}.home .features{border-top:1px solid var(--c-border);transition:border-color var(--t-color);padding:1.2rem 0;margin-top:2.5rem;display:flex;flex-wrap:wrap;align-items:flex-start;align-content:stretch;justify-content:space-between}.home .feature{flex-grow:1;flex-basis:30%;max-width:30%}.home .feature h2{font-size:1.4rem;font-weight:500;border-bottom:none;padding-bottom:0;color:var(--c-text-light)}.home .feature p,.home .footer{color:var(--c-text-lighter)}.home .footer{padding:2.5rem;border-top:1px solid var(--c-border);text-align:center;transition:border-color var(--t-color)}@media (max-width:719px){.home .features{flex-direction:column}.home .feature{max-width:100%;padding:0 2.5rem}}@media (max-width:419px){.home{padding-left:1.5rem;padding-right:1.5rem}.home .hero img{max-height:210px;margin:2rem auto 1.2rem}.home .hero h1{font-size:2rem}.home .hero .actions,.home .hero .description,.home .hero h1{margin:1.2rem auto}.home .hero .description{font-size:1.2rem}.home .hero .action-button{font-size:1rem;padding:.6rem 1.2rem}.home .feature h2{font-size:1.25rem}}.page{padding-top:var(--navbar-height);padding-left:var(--sidebar-width)}.navbar,.sidebar{position:fixed;left:0;box-sizing:border-box}.navbar{z-index:20;top:0;right:0;height:var(--navbar-height);border-bottom:1px solid var(--c-border);background-color:var(--c-bg-navbar);transition:background-color var(--t-color),border-color var(--t-color)}.sidebar{font-size:16px;width:var(--sidebar-width);z-index:10;margin:0;top:var(--navbar-height);bottom:0;border-right:1px solid var(--c-border);overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--c-brand) var(--c-border);background-color:var(--c-bg-sidebar);transition:transform var(--t-transform),background-color var(--t-color),border-color var(--t-color)}.sidebar::-webkit-scrollbar{width:7px}.sidebar::-webkit-scrollbar-track{background-color:var(--c-border)}.sidebar::-webkit-scrollbar-thumb{background-color:var(--c-brand)}.sidebar-mask{position:fixed;z-index:9;top:0;left:0;width:100vw;height:100vh;display:none}.theme-container.sidebar-open .sidebar-mask{display:block}.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(1){transform:rotate(45deg) translate3d(5.5px,5.5px,0)}.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(2){transform:scale3d(0,1,1)}.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(3){transform:rotate(-45deg) translate3d(6px,-6px,0)}.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(1),.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(3){transform-origin:center}.theme-container.no-navbar .theme-default-content:not(.custom)>h1,.theme-container.no-navbar h2,.theme-container.no-navbar h3,.theme-container.no-navbar h4,.theme-container.no-navbar h5,.theme-container.no-navbar h6{margin-top:1.5rem;padding-top:0}.theme-container.no-navbar .page{padding-top:0}.theme-container.no-navbar .sidebar{top:0}@media (min-width:720px){.theme-container.no-sidebar .sidebar{display:none}.theme-container.no-sidebar .page{padding-left:0}}.theme-default-content:not(.custom)>h1,.theme-default-content:not(.custom)>h2,.theme-default-content:not(.custom)>h3,.theme-default-content:not(.custom)>h4,.theme-default-content:not(.custom)>h5,.theme-default-content:not(.custom)>h6{margin-top:calc(.5rem - var(--navbar-height));padding-top:calc(1rem + var(--navbar-height));margin-bottom:0}.theme-default-content:not(.custom)>h1:first-child,.theme-default-content:not(.custom)>h2:first-child,.theme-default-content:not(.custom)>h3:first-child,.theme-default-content:not(.custom)>h4:first-child,.theme-default-content:not(.custom)>h5:first-child,.theme-default-content:not(.custom)>h6:first-child{margin-bottom:1rem}.theme-default-content:not(.custom)>h1:first-child+.custom-container,.theme-default-content:not(.custom)>h1:first-child+p,.theme-default-content:not(.custom)>h1:first-child+pre,.theme-default-content:not(.custom)>h2:first-child+.custom-container,.theme-default-content:not(.custom)>h2:first-child+p,.theme-default-content:not(.custom)>h2:first-child+pre,.theme-default-content:not(.custom)>h3:first-child+.custom-container,.theme-default-content:not(.custom)>h3:first-child+p,.theme-default-content:not(.custom)>h3:first-child+pre,.theme-default-content:not(.custom)>h4:first-child+.custom-container,.theme-default-content:not(.custom)>h4:first-child+p,.theme-default-content:not(.custom)>h4:first-child+pre,.theme-default-content:not(.custom)>h5:first-child+.custom-container,.theme-default-content:not(.custom)>h5:first-child+p,.theme-default-content:not(.custom)>h5:first-child+pre,.theme-default-content:not(.custom)>h6:first-child+.custom-container,.theme-default-content:not(.custom)>h6:first-child+p,.theme-default-content:not(.custom)>h6:first-child+pre{margin-top:2rem}.theme-default-content:not(.custom){max-width:var(--content-width);margin:0 auto;padding:2rem 2.5rem;padding-top:0}@media (max-width:959px){.theme-default-content:not(.custom){padding:2rem}}@media (max-width:419px){.theme-default-content:not(.custom){padding:1.5rem}}.theme-default-content:not(.custom) a:hover{text-decoration:underline}.theme-default-content:not(.custom) img{max-width:100%}.theme-default-content.custom{padding:0;margin:0}.theme-default-content.custom img{max-width:100%}@media (max-width:959px){.sidebar{font-size:15px;width:var(--sidebar-width-mobile)}.page{padding-left:var(--sidebar-width-mobile)}}@media (max-width:719px){.sidebar{top:0;padding-top:var(--navbar-height);transform:translateX(-100%)}.page{padding-left:0}.theme-container.sidebar-open .sidebar{transform:translateX(0)}.theme-container.no-navbar .sidebar{padding-top:0}}@media (max-width:419px){h1{font-size:1.9rem}}.navbar{--navbar-line-height:calc( var(--navbar-height) - 2 * var(--navbar-padding-v) );padding:var(--navbar-padding-v) var(--navbar-padding-h);line-height:var(--navbar-line-height)}.navbar .logo{height:var(--navbar-line-height);margin-right:var(--navbar-padding-v);vertical-align:top}.navbar .site-name{font-size:1.3rem;font-weight:600;color:var(--c-text);position:relative}.navbar .navbar-items-wrapper{display:flex;position:absolute;box-sizing:border-box;top:var(--navbar-padding-v);right:var(--navbar-padding-h);height:var(--navbar-line-height);padding-left:var(--navbar-padding-h);white-space:nowrap;font-size:.9rem}.navbar .navbar-items-wrapper .search-box{flex:0 0 auto;vertical-align:top}@media (max-width:719px){.navbar{padding-left:4rem}.navbar .can-hide{display:none}.navbar .site-name{width:calc(100vw - 9.4rem);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}}.navbar-items,.navbar-items a{display:inline-block}.navbar-items a{line-height:1.4rem;color:inherit}.navbar-items a.router-link-active,.navbar-items a:hover{color:var(--c-text-accent)}.navbar-items .navbar-item{position:relative;display:inline-block;margin-left:1.5rem;line-height:var(--navbar-line-height)}.navbar-items .navbar-item:first-child{margin-left:0}@media (max-width:719px){.navbar-items .navbar-item{margin-left:0}}@media (min-width:719px){.navbar-items a.router-link-active,.navbar-items a:hover{color:var(--c-text)}.navbar-item>a.router-link-active,.navbar-item>a:hover{margin-bottom:-2px;border-bottom:2px solid var(--c-text-accent)}}.toggle-sidebar-button{position:absolute;top:.6rem;left:1rem;display:none;padding:.6rem;cursor:pointer}.toggle-sidebar-button .icon{display:flex;flex-direction:column;justify-content:center;align-items:center;width:1.25rem;height:1.25rem;cursor:inherit}.toggle-sidebar-button .icon span{display:inline-block;width:100%;height:2px;border-radius:2px;background-color:var(--c-text);transition:transform var(--t-transform)}.toggle-sidebar-button .icon span:nth-child(2){margin:6px 0}@media screen and (max-width:719px){.toggle-sidebar-button{display:block}}.toggle-dark-button{display:flex;margin:auto;margin-left:1rem;border:0;background:0 0;color:var(--c-text);opacity:.8;cursor:pointer}.toggle-dark-button:hover{opacity:1}.toggle-dark-button .icon{width:1.25rem;height:1.25rem}.DocSearch{transition:background-color var(--t-color)}.navbar-dropdown-wrapper{cursor:pointer}.navbar-dropdown-wrapper .navbar-dropdown-title,.navbar-dropdown-wrapper .navbar-dropdown-title-mobile{display:block;font-size:.9rem;font-family:inherit;cursor:inherit;padding:inherit;line-height:1.4rem;background:0 0;border:0;font-weight:500;color:var(--c-text)}.navbar-dropdown-wrapper .navbar-dropdown-title-mobile{display:none;font-weight:600;font-size:inherit}.navbar-dropdown-wrapper .navbar-dropdown-title-mobile:hover,.navbar-dropdown-wrapper .navbar-dropdown-title:hover{border-color:transparent}.navbar-dropdown-wrapper .navbar-dropdown-title .arrow,.navbar-dropdown-wrapper .navbar-dropdown-title-mobile .arrow{vertical-align:middle;margin-top:-1px;margin-left:.4rem}.navbar-dropdown-wrapper .navbar-dropdown-title-mobile:hover{color:var(--c-text-accent)}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item{color:inherit;line-height:1.7rem}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subtitle{margin:.45rem 0 0;border-top:1px solid var(--c-border);padding:1rem 0 .45rem;font-size:.9rem}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subtitle>span{padding:0 1.5rem 0 1.25rem}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subtitle>a{font-weight:inherit}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subitem-wrapper{padding:0;list-style:none}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subitem-wrapper .navbar-dropdown-subitem{font-size:.9em}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item a{display:block;line-height:1.7rem;position:relative;border-bottom:none;font-weight:400;margin-bottom:0;padding:0 1.5rem 0 1.25rem}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item a.router-link-active,.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item a:hover{color:var(--c-text-accent)}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item a.router-link-active::after{content:"";width:0;height:0;border-left:5px solid var(--c-text-accent);border-top:3px solid transparent;border-bottom:3px solid transparent;position:absolute;top:calc(50% - 2px);left:9px}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item:first-child .navbar-dropdown-subtitle{margin-top:0;padding-top:0;border-top:0}@media (max-width:719px){.navbar-dropdown-wrapper.open .navbar-dropdown-title,.navbar-dropdown-wrapper.open .navbar-dropdown-title-mobile{margin-bottom:.5rem}.navbar-dropdown-wrapper .navbar-dropdown-title{display:none}.navbar-dropdown-wrapper .navbar-dropdown-title-mobile{display:block}.navbar-dropdown-wrapper .navbar-dropdown{transition:height .1s ease-out;overflow:hidden}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subtitle{border-top:0;margin-top:0;padding-top:0;padding-bottom:0;font-size:15px;line-height:2rem}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item>a{font-size:15px;line-height:2rem}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subitem{font-size:14px;padding-left:1rem}}@media (min-width:720px){.navbar-dropdown-wrapper{height:1.8rem}.navbar-dropdown-wrapper.open .navbar-dropdown,.navbar-dropdown-wrapper:hover .navbar-dropdown{display:block!important}.navbar-dropdown-wrapper.open:blur{display:none}.navbar-dropdown-wrapper .navbar-dropdown{display:none;height:auto!important;box-sizing:border-box;max-height:calc(100vh - 2.7rem);overflow-y:auto;position:absolute;top:100%;right:0;background-color:var(--c-bg-navbar);padding:.6rem 0;border:1px solid var(--c-border);border-bottom-color:var(--c-border-dark);text-align:left;border-radius:.25rem;white-space:nowrap;margin:0}}.page{padding-bottom:2rem;display:block}.page-meta{max-width:var(--content-width);margin:0 auto;padding:1rem 2.5rem;overflow:auto}@media (max-width:959px){.page-meta{padding:2rem}}@media (max-width:419px){.page-meta{padding:1.5rem}}.page-meta .meta-item{cursor:default;margin-top:.8rem}.page-meta .meta-item .meta-item-label{font-weight:500;color:var(--c-text-lighter)}.page-meta .meta-item .meta-item-info{font-weight:400;color:var(--c-text-quote)}.page-meta .edit-link{display:inline-block;margin-right:.25rem}.page-meta .last-updated{float:right}@media (max-width:719px){.page-meta .last-updated{font-size:.8em;float:none}.page-meta .contributors{font-size:.8em}}.page-nav{max-width:var(--content-width);margin:0 auto;padding:1rem 2.5rem 2rem;padding-bottom:0}@media (max-width:959px){.page-nav{padding:2rem}}@media (max-width:419px){.page-nav{padding:1.5rem}}.page-nav .inner{min-height:2rem;margin-top:0;border-top:1px solid var(--c-border);transition:border-color var(--t-color);padding-top:1rem;overflow:auto}.page-nav .prev a:before{content:"←"}.page-nav .next{float:right}.page-nav .next a:after{content:"→"}.sidebar ul{padding:0;margin:0;list-style-type:none}.sidebar a{display:inline-block}.sidebar .navbar-items{display:none;border-bottom:1px solid var(--c-border);transition:border-color var(--t-color);padding:.5rem 0 .75rem}.sidebar .navbar-items a{font-weight:600}.sidebar .navbar-items .navbar-item{display:block;line-height:1.25rem;font-size:1.1em;padding:.5rem 0 .5rem 1.5rem}.sidebar .sidebar-items{padding:1.5rem 0}@media (max-width:719px){.sidebar .navbar-items{display:block}.sidebar .navbar-items .navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item a.router-link-active::after{top:calc(1rem - 2px)}.sidebar .sidebar-items{padding:1rem 0}}.sidebar-item{cursor:default;border-left:.25rem solid transparent;color:var(--c-text)}.sidebar-item:focus-visible{outline-width:1px;outline-offset:-1px}.sidebar-item.active:not(p.sidebar-heading){font-weight:600;color:var(--c-text-accent);border-left-color:var(--c-text-accent)}.sidebar-item.sidebar-heading{transition:color .15s ease;font-size:1.1em;font-weight:700;padding:.35rem 1.5rem .35rem 1.25rem;width:100%;box-sizing:border-box;margin:0}.sidebar-item.sidebar-heading.collapsible,a.sidebar-item{cursor:pointer}.sidebar-item.sidebar-heading.collapsible+.sidebar-item-children{transition:height .1s ease-out;overflow:hidden;margin-bottom:.75rem}.sidebar-item.sidebar-heading .arrow{position:relative;top:-.12em;left:.5em}.sidebar-item:not(.sidebar-heading){font-size:1em;font-weight:400;display:inline-block;margin:0;padding:.35rem 1rem .35rem 2rem;line-height:1.4;width:100%;box-sizing:border-box}.sidebar-item:not(.sidebar-heading)+.sidebar-item-children{padding-left:1rem;font-size:.95em}.sidebar-item-children .sidebar-item-children .sidebar-item:not(.sidebar-heading){padding:.25rem 1rem .25rem 1.75rem}.sidebar-item-children .sidebar-item-children .sidebar-item:not(.sidebar-heading).active{font-weight:500;border-left-color:transparent}a.sidebar-heading+.sidebar-item-children .sidebar-item:not(.sidebar-heading).active{border-left-color:transparent}a.sidebar-item:hover{color:var(--c-text-accent)}.table-of-contents .badge{vertical-align:middle}.dropdown-enter-from,.dropdown-leave-to{height:0!important}.fade-slide-y-enter-active{transition:all .2s ease}.fade-slide-y-leave-active{transition:all .2s cubic-bezier(1,.5,.8,1)}.fade-slide-y-enter-from,.fade-slide-y-leave-to{transform:translateY(10px);opacity:0}
|
|
6
|
-
.resize-observer[data-v-b329ee4c]{border:0;background-color:transparent;opacity:0}.resize-observer[data-v-b329ee4c],.resize-observer[data-v-b329ee4c] object{display:block;position:absolute;top:0;left:0;height:100%;width:100%;overflow:hidden;pointer-events:none;z-index:-1}.v-popper__popper{z-index:10000;top:0;left:0}.v-popper__popper.v-popper__popper--hidden{visibility:hidden;opacity:0;transition:opacity .15s,visibility .15s;pointer-events:none}.v-popper__popper.v-popper__popper--shown{visibility:visible;opacity:1;transition:opacity .15s}.v-popper__popper.v-popper__popper--skip-transition,.v-popper__popper.v-popper__popper--skip-transition>.v-popper__wrapper{transition:none!important}.v-popper__backdrop{position:absolute;top:0;left:0;width:100%;height:100%;display:none}.v-popper__inner{position:relative;box-sizing:border-box;overflow-y:auto}.v-popper__inner>div{position:relative;z-index:1;max-width:inherit;max-height:inherit}.v-popper__arrow-container{position:absolute;width:10px;height:10px}.v-popper__popper--arrow-overflow .v-popper__arrow-container,.v-popper__popper--no-positioning .v-popper__arrow-container{display:none}.v-popper__arrow-inner,.v-popper__arrow-outer{border-style:solid;position:absolute;top:0;left:0;width:0;height:0}.v-popper__arrow-inner{visibility:hidden;border-width:7px}.v-popper__arrow-outer{border-width:6px}.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-inner,.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-inner{left:-2px}.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-outer,.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-outer{left:-1px}.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-inner,.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-outer{border-bottom-width:0;border-left-color:transparent!important;border-right-color:transparent!important;border-bottom-color:transparent!important}.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-inner{top:-2px}.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-container{top:0}.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-inner,.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-outer{border-top-width:0;border-left-color:transparent!important;border-right-color:transparent!important;border-top-color:transparent!important}.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-inner{top:-4px}.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-outer{top:-6px}.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-inner,.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-inner{top:-2px}.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-outer,.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-outer{top:-1px}.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-inner,.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-outer{border-left-width:0;border-left-color:transparent!important;border-top-color:transparent!important;border-bottom-color:transparent!important}.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-inner{left:-4px}.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-outer{left:-6px}.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-container{right:-10px}.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-inner,.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-outer{border-right-width:0;border-top-color:transparent!important;border-right-color:transparent!important;border-bottom-color:transparent!important}.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-inner{left:-2px}.v-popper{width:-webkit-max-content;width:-moz-max-content;width:max-content}.v-popper--theme-dropdown .v-popper__inner{background:#fff;color:#000;border-radius:6px;border:1px solid #ddd;box-shadow:0 6px 30px #0000001a}.v-popper--theme-dropdown .v-popper__arrow-inner{visibility:visible;border-color:#fff}.v-popper--theme-dropdown .v-popper__arrow-outer{border-color:#ddd}.v-popper--theme-tooltip .v-popper__inner{background:rgba(0,0,0,.8);color:#fff;border-radius:6px;padding:7px 12px 6px}.v-popper--theme-tooltip .v-popper__arrow-outer{border-color:#000c}
|
|
7
|
-
:root{--back-to-top-z-index:5;--back-to-top-color:#3eaf7c;--back-to-top-color-hover:#71cda3}
|
|
8
|
-
.back-to-top{cursor:pointer;position:fixed;bottom:2rem;right:2.5rem;width:2rem;height:1.2rem;background-color:var(--back-to-top-color);-webkit-mask:url(/assets/img/back-to-top.8b37f773.svg)no-repeat;mask:url(/assets/img/back-to-top.8b37f773.svg)no-repeat;z-index:var(--back-to-top-z-index)}.back-to-top:hover{background-color:var(--back-to-top-color-hover)}@media (max-width:959px){.back-to-top{display:none}}.back-to-top-enter-active,.back-to-top-leave-active{transition:opacity .3s}.back-to-top-enter-from,.back-to-top-leave-to{opacity:0}
|
|
9
|
-
:root{--nprogress-color:#29d;--nprogress-z-index:1031}
|
|
10
|
-
#nprogress{pointer-events:none}#nprogress .bar{background:var(--nprogress-color);position:fixed;z-index:var(--nprogress-z-index);top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px var(--nprogress-color),0 0 5px var(--nprogress-color);opacity:1;transform:rotate(3deg) translate(0,-4px)}
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 49.484 28.284"><g transform="translate(-229 -126.358)" fill="currentColor"><rect width="35" height="5" rx="2" transform="rotate(-45 296.902 -200.874)"/><rect width="35" height="5" rx="2" transform="rotate(-135 169.502 20.377)"/></g></svg>
|
|
Binary file
|