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.
Files changed (132) hide show
  1. package/.github/workflows/{firebase-hosting-merge.yml → publish-doc-on-merge.yml} +6 -6
  2. package/.github/workflows/publish-to-npm.yml +23 -0
  3. package/.github/workflows/test-on-pull-request.yaml +13 -0
  4. package/docs/.vuepress/components/BestSaveVerificator.vue +18 -7
  5. package/docs/.vuepress/components/DonateButtons.vue +0 -1
  6. package/docs/.vuepress/config.js +9 -58
  7. package/docs/.vuepress/navbar.js +43 -0
  8. package/docs/changelog/README.md +18 -0
  9. package/docs/examples/example-cascade-temperature-control.md +0 -13
  10. package/docs/examples/example-heat-capacitor.md +9 -20
  11. package/docs/examples/example-nordpool-current-state.md +0 -13
  12. package/docs/examples/example-nordpool-events-state.md +0 -13
  13. package/docs/examples/example-tibber-mqtt.md +0 -13
  14. package/docs/faq/best-save-viewer.md +1 -0
  15. package/docs/guide/README.md +20 -12
  16. package/docs/images/best-save-config.png +0 -0
  17. package/docs/images/lowest-price-config.png +0 -0
  18. package/docs/nodes/ps-strategy-best-save.md +67 -11
  19. package/docs/nodes/ps-strategy-lowest-price.md +69 -0
  20. package/package.json +9 -10
  21. package/src/elvia/elvia-add-tariff.html +1 -1
  22. package/src/handle-input.js +30 -10
  23. package/src/receive-price-functions.js +2 -0
  24. package/src/strategy-best-save.html +14 -0
  25. package/src/strategy-best-save.js +6 -2
  26. package/src/strategy-lowest-price.html +15 -2
  27. package/src/strategy-lowest-price.js +3 -1
  28. package/src/utils.js +3 -3
  29. package/test/data/lowest-price-result-cont.json +1 -0
  30. package/test/data/lowest-price-result-missing-end.json +1 -0
  31. package/test/data/lowest-price-result-split-allday.json +1 -0
  32. package/test/data/lowest-price-result-split-allday10.json +1 -0
  33. package/test/data/lowest-price-result-split.json +1 -0
  34. package/test/data/tibber-result-end-0-24h.json +1 -0
  35. package/test/data/tibber-result-end-0.json +1 -0
  36. package/.github/workflows/firebase-hosting-pull-request.yml +0 -17
  37. package/docs/.vuepress/dist/.nojekyll +0 -0
  38. package/docs/.vuepress/dist/404.html +0 -33
  39. package/docs/.vuepress/dist/Ukraine-heart-shape-flag.png +0 -0
  40. package/docs/.vuepress/dist/assets/css/835.styles.c5afb22b.css +0 -1
  41. package/docs/.vuepress/dist/assets/css/896.styles.21a80cb6.css +0 -1
  42. package/docs/.vuepress/dist/assets/css/styles.1c48cbd0.css +0 -10
  43. package/docs/.vuepress/dist/assets/img/add-tariff-flow.eb700d4f.png +0 -0
  44. package/docs/.vuepress/dist/assets/img/back-to-top.8b37f773.svg +0 -1
  45. package/docs/.vuepress/dist/assets/img/best-save-config.79a2f39a.png +0 -0
  46. package/docs/.vuepress/dist/assets/img/copy-payload-best-save.b9192985.png +0 -0
  47. package/docs/.vuepress/dist/assets/img/elvia-config-no-config.b4bb972c.png +0 -0
  48. package/docs/.vuepress/dist/assets/img/elvia-config-no-tariff.3f89aba8.png +0 -0
  49. package/docs/.vuepress/dist/assets/img/elvia-config-select-tariff.0f73fd56.png +0 -0
  50. package/docs/.vuepress/dist/assets/img/elvia-config-subscription-key.8be8ab8a.png +0 -0
  51. package/docs/.vuepress/dist/assets/img/elvia-flow.bae2a4d5.png +0 -0
  52. package/docs/.vuepress/dist/assets/img/example-flow-1.3ff3e23f.png +0 -0
  53. package/docs/.vuepress/dist/assets/img/example-flow-2.b653b58d.png +0 -0
  54. package/docs/.vuepress/dist/assets/img/heat-capacitor-temperatureVsPrice.6e74905b.png +0 -0
  55. package/docs/.vuepress/dist/assets/img/lowest-price-config.6d66a8c2.png +0 -0
  56. package/docs/.vuepress/dist/assets/img/migrate-best-save.f73420f6.png +0 -0
  57. package/docs/.vuepress/dist/assets/img/migrate-power-saver.aae13f9d.png +0 -0
  58. package/docs/.vuepress/dist/assets/img/next-schedule-entity.4406856a.png +0 -0
  59. package/docs/.vuepress/dist/assets/img/next-schedule-flow.413ad62b.png +0 -0
  60. package/docs/.vuepress/dist/assets/img/next-schedule-sensor.eb896bdd.png +0 -0
  61. package/docs/.vuepress/dist/assets/img/node-power-saver.51ff2e5d.png +0 -0
  62. package/docs/.vuepress/dist/assets/img/node-ps-elvia-add-tariff.94ea2b09.png +0 -0
  63. package/docs/.vuepress/dist/assets/img/node-ps-general-add-tariff.a3cf6f06.png +0 -0
  64. package/docs/.vuepress/dist/assets/img/node-ps-receive-price.76eaa418.png +0 -0
  65. package/docs/.vuepress/dist/assets/img/node-ps-strategy-best-save.392292d5.png +0 -0
  66. package/docs/.vuepress/dist/assets/img/node-ps-strategy-heat-capacitor-cascade-control.2e75ed9e.png +0 -0
  67. package/docs/.vuepress/dist/assets/img/node-ps-strategy-heat-capacitor-simple-flow-example.29d9bf59.png +0 -0
  68. package/docs/.vuepress/dist/assets/img/node-ps-strategy-lowest-price.3a4ad347.png +0 -0
  69. package/docs/.vuepress/dist/assets/img/oven-setpoint-calculation.5bda0eec.png +0 -0
  70. package/docs/.vuepress/dist/assets/img/overshoot-time.b3b5d70e.png +0 -0
  71. package/docs/.vuepress/dist/assets/img/power-saver-nordpool-current-state.bf14afde.png +0 -0
  72. package/docs/.vuepress/dist/assets/img/power-saver-nordpool-events-state.8c392507.png +0 -0
  73. package/docs/.vuepress/dist/assets/img/power-saver-tibber-mqtt.16891dd2.png +0 -0
  74. package/docs/.vuepress/dist/assets/js/229.5c5378fa.js +0 -1
  75. package/docs/.vuepress/dist/assets/js/331.872104cd.js +0 -1
  76. package/docs/.vuepress/dist/assets/js/405.f4edd94d.js +0 -2
  77. package/docs/.vuepress/dist/assets/js/405.f4edd94d.js.LICENSE.txt +0 -8
  78. package/docs/.vuepress/dist/assets/js/490.1e639e05.js +0 -1
  79. package/docs/.vuepress/dist/assets/js/491.bd938119.js +0 -1
  80. package/docs/.vuepress/dist/assets/js/555.d8963d84.js +0 -1
  81. package/docs/.vuepress/dist/assets/js/811.5f659592.js +0 -1
  82. package/docs/.vuepress/dist/assets/js/app.6c6c1409.js +0 -1
  83. package/docs/.vuepress/dist/assets/js/runtime~app.f6ac32d7.js +0 -1
  84. package/docs/.vuepress/dist/assets/js/v-0607240a.0193a377.js +0 -1
  85. package/docs/.vuepress/dist/assets/js/v-08683c60.52e94cb6.js +0 -1
  86. package/docs/.vuepress/dist/assets/js/v-0aca7ba6.cac5d4b9.js +0 -1
  87. package/docs/.vuepress/dist/assets/js/v-0b5e3c8c.18561f6e.js +0 -1
  88. package/docs/.vuepress/dist/assets/js/v-1ad821fa.6697a349.js +0 -1
  89. package/docs/.vuepress/dist/assets/js/v-1b3a0ab8.c6c4e19b.js +0 -1
  90. package/docs/.vuepress/dist/assets/js/v-1e2b191e.07b8ab21.js +0 -1
  91. package/docs/.vuepress/dist/assets/js/v-29504124.00be7399.js +0 -1
  92. package/docs/.vuepress/dist/assets/js/v-30acb564.28af12af.js +0 -1
  93. package/docs/.vuepress/dist/assets/js/v-3706649a.c76d575b.js +0 -1
  94. package/docs/.vuepress/dist/assets/js/v-4637f9e4.d334c29a.js +0 -1
  95. package/docs/.vuepress/dist/assets/js/v-4c28314d.8cbb0f9d.js +0 -1
  96. package/docs/.vuepress/dist/assets/js/v-510ed0d4.c04bc2e4.js +0 -1
  97. package/docs/.vuepress/dist/assets/js/v-5954bcb2.dff3fc67.js +0 -1
  98. package/docs/.vuepress/dist/assets/js/v-5db8da3a.e5e6d7a6.js +0 -1
  99. package/docs/.vuepress/dist/assets/js/v-61f728ca.81968036.js +0 -1
  100. package/docs/.vuepress/dist/assets/js/v-677dfaed.c159b0f4.js +0 -1
  101. package/docs/.vuepress/dist/assets/js/v-7446a652.8fc2c591.js +0 -1
  102. package/docs/.vuepress/dist/assets/js/v-7c87f26e.8ed52391.js +0 -1
  103. package/docs/.vuepress/dist/assets/js/v-84304104.f3f07ed3.js +0 -1
  104. package/docs/.vuepress/dist/assets/js/v-8daa1a0e.ed84ca09.js +0 -1
  105. package/docs/.vuepress/dist/assets/js/v-b4a42144.9a2a0c9f.js +0 -1
  106. package/docs/.vuepress/dist/assets/js/v-e8c55052.b7d52fc6.js +0 -1
  107. package/docs/.vuepress/dist/assets/js/v-fffb8e28.d09ab959.js +0 -1
  108. package/docs/.vuepress/dist/changelog/index.html +0 -33
  109. package/docs/.vuepress/dist/contribute/index.html +0 -33
  110. package/docs/.vuepress/dist/euro.png +0 -0
  111. package/docs/.vuepress/dist/examples/example-cascade-temperature-control.html +0 -304
  112. package/docs/.vuepress/dist/examples/example-heat-capacitor.html +0 -247
  113. package/docs/.vuepress/dist/examples/example-next-schedule-entity.html +0 -43
  114. package/docs/.vuepress/dist/examples/example-nordpool-current-state.html +0 -206
  115. package/docs/.vuepress/dist/examples/example-nordpool-events-state.html +0 -191
  116. package/docs/.vuepress/dist/examples/example-tibber-mqtt.html +0 -199
  117. package/docs/.vuepress/dist/examples/index.html +0 -33
  118. package/docs/.vuepress/dist/faq/best-save-viewer.html +0 -33
  119. package/docs/.vuepress/dist/faq/index.html +0 -33
  120. package/docs/.vuepress/dist/guide/index.html +0 -70
  121. package/docs/.vuepress/dist/index.html +0 -33
  122. package/docs/.vuepress/dist/logo.png +0 -0
  123. package/docs/.vuepress/dist/nodes/index.html +0 -33
  124. package/docs/.vuepress/dist/nodes/old-power-saver-doc.html +0 -115
  125. package/docs/.vuepress/dist/nodes/power-saver.html +0 -33
  126. package/docs/.vuepress/dist/nodes/ps-elvia-add-tariff.html +0 -33
  127. package/docs/.vuepress/dist/nodes/ps-general-add-tariff.html +0 -33
  128. package/docs/.vuepress/dist/nodes/ps-receive-price.html +0 -98
  129. package/docs/.vuepress/dist/nodes/ps-strategy-best-save.html +0 -104
  130. package/docs/.vuepress/dist/nodes/ps-strategy-heat-capacitor.html +0 -260
  131. package/docs/.vuepress/dist/nodes/ps-strategy-lowest-price.html +0 -124
  132. 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.4.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-webpack": "^2.0.0-alpha.1",
49
- "@vuepress/plugin-register-components": "^2.0.0-beta.33",
50
- "@vuepress/utils": "^2.0.0-beta.27",
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.0",
54
+ "node-red": "^2.2.2",
54
55
  "node-red-node-test-helper": "^0.2.7",
55
- "patch-vue-directive-ssr": "^0.0.1",
56
- "vuepress": "^2.0.0-beta.35",
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.0",
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.
@@ -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.context().set(["lastPlan", "lastPriceData", "lastSource"], [undefined, undefined, undefined]);
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
- const { priceData, source } = getPriceData(node, msg);
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-arrows-h"></i> From time</label>
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-arrows-h"></i> To time</label>
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: [],
@@ -7,6 +7,7 @@
7
7
  { "time": "2021-10-12T16:00:00.000+02:00", "value": false }
8
8
  ],
9
9
  "config": {
10
+ "contextStorage": "default",
10
11
  "doNotSplit": true,
11
12
  "fromTime": "10",
12
13
  "hoursOn": 4,
@@ -306,6 +306,7 @@
306
306
  ],
307
307
  "source": "Nordpool",
308
308
  "config": {
309
+ "contextStorage": "default",
309
310
  "fromTime": "22",
310
311
  "toTime": "08",
311
312
  "hoursOn": 3,
@@ -10,6 +10,7 @@
10
10
  { "time": "2021-10-12T15:00:00.000+02:00", "value": false }
11
11
  ],
12
12
  "config": {
13
+ "contextStorage": "default",
13
14
  "doNotSplit": false,
14
15
  "fromTime": "00",
15
16
  "hoursOn": 8,
@@ -9,6 +9,7 @@
9
9
  { "time": "2021-10-12T10:00:00.000+02:00", "value": true }
10
10
  ],
11
11
  "config": {
12
+ "contextStorage": "default",
12
13
  "doNotSplit": false,
13
14
  "fromTime": "10",
14
15
  "hoursOn": 10,
@@ -9,6 +9,7 @@
9
9
  { "time": "2021-10-12T20:00:00.000+02:00", "value": false }
10
10
  ],
11
11
  "config": {
12
+ "contextStorage": "default",
12
13
  "doNotSplit": false,
13
14
  "fromTime": "10",
14
15
  "hoursOn": 6,
@@ -309,6 +309,7 @@
309
309
  ],
310
310
  "source": "Tibber",
311
311
  "config": {
312
+ "contextStorage": "default",
312
313
  "fromTime": "16",
313
314
  "toTime": "00",
314
315
  "hoursOn": 3,
@@ -157,6 +157,7 @@
157
157
  ],
158
158
  "source": "Tibber",
159
159
  "config": {
160
+ "contextStorage": "default",
160
161
  "fromTime": "16",
161
162
  "toTime": "00",
162
163
  "hoursOn": 3,
@@ -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&#39;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>
@@ -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)}
@@ -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>