Skip to content

Commit 547a512

Browse files
committed
Yaml editor
1 parent 1479c3c commit 547a512

20 files changed

+11898
-4469
lines changed

package-lock.json

Lines changed: 251 additions & 133 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
"main": "index.js",
66
"scripts": {
77
"start": "(node ./script/hot-reload.mjs & esbuild src/plotly-graph-card.ts --servedir=dist --outdir=dist --bundle --sourcemap=inline)",
8-
"build": "esbuild src/plotly-graph-card.ts --outdir=dist --bundle --minify && npm run schema",
8+
"build": "esbuild src/plotly-graph-card.ts --outdir=dist --bundle --minify",
99
"tsc": "tsc",
1010
"test": "jest",
1111
"test:watch": "jest --watchAll",
1212
"version": "npm run build && git add .",
13-
"npm-upgrade": "npx npm-upgrade",
14-
"schema": "typescript-json-schema ./tsconfig.json JsonSchemaRoot > schema.json"
13+
"npm-upgrade": "npx npm-upgrade"
1514
},
1615
"author": "",
1716
"license": "ISC",
@@ -45,8 +44,8 @@
4544
"ndarray": "^1.0.19",
4645
"ndarray-fft": "^1.0.3",
4746
"plotly.js": "^2.34.0",
47+
"prettier": "^3.3.3",
4848
"propose": "^0.0.5",
49-
"typescript": "^5.6.3",
50-
"typescript-json-schema": "^0.65.1"
49+
"typescript": "^5.6.3"
5150
}
5251
}

src/duration/duration.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const parseTimeDuration = (str: TimeDurationStr | undefined): number => {
4040
if (!str || !str.match)
4141
throw new Error(`Cannot parse "${str}" as a duration`);
4242
const match = str.match(
43-
/^(?<sign>[+-])?(?<number>\d*(\.\d)?)(?<unit>(ms|s|m|h|d|w|M|y))$/
43+
/^(?<sign>[+-])?(?<number>\d*(\.\d)?)(?<unit>(ms|s|m|h|d|w|M|y))$/,
4444
);
4545
if (!match || !match.groups)
4646
throw new Error(`Cannot parse "${str}" as a duration`);
@@ -87,7 +87,16 @@ export const setDateFnDefaultOptions = (hass: HomeAssistant) => {
8787
weekStartsOn,
8888
});
8989
};
90-
export const parseRelativeTime = (str: string): [number, number] => {
90+
export type RelativeTimeStr =
91+
| "current_minute"
92+
| "current_hour"
93+
| "current_day"
94+
| "current_week"
95+
| "current_month"
96+
| "current_quarter"
97+
| "current_year";
98+
99+
export const parseRelativeTime = (str: RelativeTimeStr): [number, number] => {
91100
const now = new Date();
92101
switch (str) {
93102
case "current_minute":

src/filters/filters.test.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import filters from "./filters";
1+
import filters, { FilterInput } from "./filters";
2+
3+
const RIGHT_1 = { integrate: { offset: "2d" } } satisfies FilterInput;
4+
const RIGHT_2 = "integrate" satisfies FilterInput;
5+
const RIGHT_3 = "delta" satisfies FilterInput;
6+
const RIGHT_4 = "deduplicate_adjacent" satisfies FilterInput;
7+
//@ts-expect-error
8+
const WRONG_1 = "add" satisfies FilterInput;
9+
210
const data = {
311
states: [],
412
statistics: [],
@@ -111,7 +119,7 @@ describe("filters", () => {
111119
});
112120
it("fn", () => {
113121
expect(
114-
filters.fn(`({xs,ys,...rest}) => ({xs:ys, ys:xs,...rest})`)(data)
122+
filters.fn(`({xs,ys,...rest}) => ({xs:ys, ys:xs,...rest})`)(data),
115123
).toEqual({
116124
attributes: {
117125
unit_of_measurement: "w",

src/filters/filters.ts

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,24 @@ type FilterData = {
3131
};
3232
export type FilterFn = (p: FilterData) => Partial<FilterData>;
3333

34+
type FilterParam<K extends keyof typeof filters> = Parameters<
35+
(typeof filters)[K]
36+
>[0];
37+
38+
export type FilterInput = {
39+
[K in keyof typeof filters]: FilterParam<K> extends undefined
40+
? // Case 1: Function accepts `undefined` only, return the key as a string literal
41+
K
42+
: undefined extends FilterParam<K>
43+
? object extends FilterParam<K>
44+
? // Case 2: Function accepts `object | undefined`, return union of the key or the object
45+
{ [P in K]: Exclude<FilterParam<K>, undefined> } | K
46+
: // Case 3: Function accepts only `undefined`, return the key as a string literal
47+
K
48+
: // Case 4: Function accepts only an `object`, return object type
49+
{ [P in K]: FilterParam<K> };
50+
}[keyof typeof filters];
51+
3452
const mapNumbers = (ys: YValue[], fn: (y: number, i: number) => number) =>
3553
ys.map((y, i) => {
3654
const n = castFloat(y);
@@ -79,7 +97,7 @@ const filters = {
7997
const mapping = mappingStr.map((str) => str.split("->").map(parseFloat));
8098
const regression = new LinearRegression(
8199
mapping.map(([x, _y]) => x),
82-
mapping.map(([_x, y]) => y)
100+
mapping.map(([_x, y]) => y),
83101
);
84102
return {
85103
ys: regression.predict(ys.map(castFloat)),
@@ -150,7 +168,7 @@ const filters = {
150168
unit?: keyof typeof timeUnits;
151169
reset_every?: TimeDurationStr;
152170
offset?: TimeDurationStr;
153-
} = "h"
171+
} = "h",
154172
) => {
155173
const param =
156174
typeof unitOrObject == "string" ? { unit: unitOrObject } : unitOrObject;
@@ -197,7 +215,11 @@ const filters = {
197215
};
198216
},
199217
sliding_window_moving_average:
200-
({ window_size = 10, extended = false, centered = true } = {}) =>
218+
({
219+
window_size = 10,
220+
extended = false,
221+
centered = true,
222+
}: { window_size?: number; extended?: boolean; centered?: boolean } = {}) =>
201223
(params) => {
202224
const { xs, ys, ...rest } = force_numeric(params);
203225
const ys2: number[] = [];
@@ -227,7 +249,11 @@ const filters = {
227249
return { xs: xs2, ys: ys2, ...rest };
228250
},
229251
median:
230-
({ window_size = 10, extended = false, centered = true } = {}) =>
252+
({
253+
window_size = 10,
254+
extended = false,
255+
centered = true,
256+
}: { window_size?: number; extended?: boolean; centered?: boolean } = {}) =>
231257
(params) => {
232258
const { xs, ys, ...rest } = force_numeric(params);
233259
const ys2: number[] = [];
@@ -257,7 +283,7 @@ const filters = {
257283
return { ys: ys2, xs: xs2, ...rest };
258284
},
259285
exponential_moving_average:
260-
({ alpha = 0.1 } = {}) =>
286+
({ alpha = 0.1 }: { alpha?: number } = {}) =>
261287
(params) => {
262288
const { ys, ...rest } = force_numeric(params);
263289
let last = ys[0];
@@ -268,37 +294,37 @@ const filters = {
268294
},
269295
map_y_numbers: (fnStr: string) => {
270296
const fn = myEval(
271-
`(i, x, y, state, statistic, xs, ys, states, statistics, meta, vars, hass) => ${fnStr}`
297+
`(i, x, y, state, statistic, xs, ys, states, statistics, meta, vars, hass) => ${fnStr}`,
272298
);
273299
return ({ xs, ys, states, statistics, meta, vars, hass }) => ({
274300
xs,
275301
ys: mapNumbers(ys, (_, i) =>
276302
// prettier-ignore
277-
fn(i, xs[i], ys[i], states[i], statistics[i], xs, ys, states, statistics, meta, vars, hass)
303+
fn(i, xs[i], ys[i], states[i], statistics[i], xs, ys, states, statistics, meta, vars, hass),
278304
),
279305
});
280306
},
281307
map_y: (fnStr: string) => {
282308
const fn = myEval(
283-
`(i, x, y, state, statistic, xs, ys, states, statistics, meta, vars, hass) => ${fnStr}`
309+
`(i, x, y, state, statistic, xs, ys, states, statistics, meta, vars, hass) => ${fnStr}`,
284310
);
285311
return ({ xs, ys, states, statistics, meta, vars, hass }) => ({
286312
xs,
287313
ys: ys.map((_, i) =>
288314
// prettier-ignore
289-
fn(i, xs[i], ys[i], states[i], statistics[i], xs, ys, states, statistics, meta, vars, hass)
315+
fn(i, xs[i], ys[i], states[i], statistics[i], xs, ys, states, statistics, meta, vars, hass),
290316
),
291317
});
292318
},
293319
map_x: (fnStr: string) => {
294320
const fn = myEval(
295-
`(i, x, y, state, statistic, xs, ys, states, statistics, meta, vars, hass) => ${fnStr}`
321+
`(i, x, y, state, statistic, xs, ys, states, statistics, meta, vars, hass) => ${fnStr}`,
296322
);
297323
return ({ xs, ys, states, statistics, meta, vars, hass }) => ({
298324
ys,
299325
xs: xs.map((_, i) =>
300326
// prettier-ignore
301-
fn(i, xs[i], ys[i], states[i], statistics[i], xs, ys, states, statistics, meta, vars, hass)
327+
fn(i, xs[i], ys[i], states[i], statistics[i], xs, ys, states, statistics, meta, vars, hass),
302328
),
303329
});
304330
},
@@ -357,31 +383,31 @@ const filters = {
357383
throw new Error(
358384
`Trendline '${p.type}' doesn't exist. Did you mean <b>${propose(
359385
p.type,
360-
Object.keys(trendlineTypes)
361-
)}<b>?\nOthers: ${Object.keys(trendlineTypes)}`
386+
Object.keys(trendlineTypes),
387+
)}<b>?\nOthers: ${Object.keys(trendlineTypes)}`,
362388
);
363389
}
364390
const regression: BaseRegression = new RegressionClass(
365391
xs_numbers,
366392
ys,
367-
p.degree
393+
p.degree,
368394
);
369395
let extras: string[] = [];
370396
if (p.show_r2)
371397
extras.push(
372-
`r²=${maxDecimals(regression.score(xs_numbers, ys).r2, 2)}`
398+
`r²=${maxDecimals(regression.score(xs_numbers, ys).r2, 2)}`,
373399
);
374400

375401
if (forecast > 0) {
376402
const N = Math.round(
377403
(xs_numbers.length /
378404
(xs_numbers[xs_numbers.length - 1] - xs_numbers[0])) *
379-
forecast
405+
forecast,
380406
);
381407
xs_numbers.push(
382408
...Array.from({ length: N }).map(
383-
(_, i) => t1 - t0 + (forecast / N) * i
384-
)
409+
(_, i) => t1 - t0 + (forecast / N) * i,
410+
),
385411
);
386412
}
387413
const ys_out = regression.predict(xs_numbers);
@@ -405,12 +431,12 @@ const filters = {
405431
*/
406432
filter: (fnStr: string) => {
407433
const fn = myEval(
408-
`(i, x, y, state, statistic, xs, ys, states, statistics, meta, vars, hass) => ${fnStr}`
434+
`(i, x, y, state, statistic, xs, ys, states, statistics, meta, vars, hass) => ${fnStr}`,
409435
);
410436
return ({ xs, ys, states, statistics, meta, vars, hass }) => {
411437
const mask = ys.map((_, i) =>
412438
// prettier-ignore
413-
fn(i, xs[i], ys[i], states[i], statistics[i], xs, ys, states, statistics, meta, vars, hass)
439+
fn(i, xs[i], ys[i], states[i], statistics[i], xs, ys, states, statistics, meta, vars, hass),
414440
);
415441
return {
416442
ys: ys.filter((_, i) => mask[i]),
@@ -425,7 +451,7 @@ export default filters;
425451
function checkTimeUnits(unit: string) {
426452
if (!timeUnits[unit]) {
427453
throw new Error(
428-
`Unit '${unit}' is not valid, use ${Object.keys(timeUnits)}`
454+
`Unit '${unit}' is not valid, use ${Object.keys(timeUnits)}`,
429455
);
430456
}
431457
}

src/types.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import {
22
ColorSchemeArray,
33
ColorSchemeNames,
44
} from "./parse-config/parse-color-scheme";
5-
import { TimeDurationStr } from "./duration/duration";
5+
6+
import { RelativeTimeStr, TimeDurationStr } from "./duration/duration";
67
import {
78
AutoPeriodConfig,
89
StatisticPeriod,
@@ -11,20 +12,24 @@ import {
1112
} from "./recorder-types";
1213

1314
import { HassEntity } from "home-assistant-js-websocket";
14-
import { FilterFn } from "./filters/filters";
15+
import { FilterFn, FilterInput } from "./filters/filters";
16+
import type filters from "./filters/filters";
17+
import internal from "stream";
18+
1519
export { HassEntity } from "home-assistant-js-websocket";
1620

1721
export type YValue = number | string | null;
1822

1923
export type InputConfig = {
2024
type: "custom:plotly-graph";
21-
hours_to_show?: number;
25+
hours_to_show?: number | TimeDurationStr | RelativeTimeStr;
2226
refresh_interval?: number | "auto"; // in seconds
2327
color_scheme?: ColorSchemeNames | ColorSchemeArray | number;
2428
title?: string;
2529
offset?: TimeDurationStr;
2630
entities: ({
2731
entity?: string;
32+
name?: string;
2833
attribute?: string;
2934
statistic?: StatisticType;
3035
period?: StatisticPeriod | "auto" | AutoPeriodConfig;
@@ -37,7 +42,7 @@ export type InputConfig = {
3742
};
3843
offset?: TimeDurationStr;
3944
extend_to_present?: boolean;
40-
filters?: (Record<string, any> | string)[];
45+
filters?: FilterInput[];
4146
on_legend_click?: Function;
4247
on_legend_dblclick?: Function;
4348
on_click?: Function;
@@ -108,20 +113,20 @@ export type EntityIdConfig =
108113
| EntityIdStatisticsConfig;
109114

110115
export function isEntityIdStateConfig(
111-
entityConfig: EntityIdConfig
116+
entityConfig: EntityIdConfig,
112117
): entityConfig is EntityIdStateConfig {
113118
return !(
114119
isEntityIdAttrConfig(entityConfig) ||
115120
isEntityIdStatisticsConfig(entityConfig)
116121
);
117122
}
118123
export function isEntityIdAttrConfig(
119-
entityConfig: EntityIdConfig
124+
entityConfig: EntityIdConfig,
120125
): entityConfig is EntityIdAttrConfig {
121126
return !!entityConfig["attribute"];
122127
}
123128
export function isEntityIdStatisticsConfig(
124-
entityConfig: EntityIdConfig
129+
entityConfig: EntityIdConfig,
125130
): entityConfig is EntityIdStatisticsConfig {
126131
return !!entityConfig["statistic"];
127132
}

tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@
1515
"experimentalDecorators": true,
1616
"allowSyntheticDefaultImports": true,
1717
"esModuleInterop": true
18-
}
18+
},
19+
"exclude": ["./yaml-editor/**"]
1920
}

yaml-editor/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/dist

yaml-editor/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Demo
2+
3+
This demo is deployed to [monaco-yaml.js.org](https://monaco-yaml.js.org). It shows how
4+
`monaco-editor` and `monaco-yaml` can be used with
5+
[Webpack 5](https://webpack.js.org/concepts/entry-points).
6+
7+
## Table of Contents
8+
9+
- [Prerequisites](#prerequisites)
10+
- [Setup](#setup)
11+
- [Running](#running)
12+
13+
## Prerequisites
14+
15+
- [NodeJS](https://nodejs.org) 16 or higher
16+
- [npm](https://github.com/npm/cli) 8.1.2 or higher
17+
18+
## Setup
19+
20+
To run the project locally, clone the repository and set it up:
21+
22+
```sh
23+
git clone https://github.com/remcohaszing/monaco-yaml
24+
cd monaco-yaml
25+
npm ci
26+
```
27+
28+
## Running
29+
30+
To start it, simply run:
31+
32+
```sh
33+
npm --workspace demo start
34+
```
35+
36+
The demo will open in your browser.

0 commit comments

Comments
 (0)