@hestia-earth/ui-components 0.43.10 → 0.43.12

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.
@@ -6899,7 +6899,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
6899
6899
 
6900
6900
  const colors = {
6901
6901
  darkBlue: 'rgba(25, 57, 87, 1)',
6902
- lightBlue: 'rgba(132, 160, 220, 1)'
6902
+ lightBlue: 'rgba(132, 160, 220, 1)',
6903
+ // `$warning` — marks the reported value, and is the one colour that reads against both a white
6904
+ // page and the dark blue popover the parametric chart is shown in
6905
+ orange: 'rgba(255, 136, 27, 1)'
6903
6906
  };
6904
6907
  const opaqueColor = (color, opacity = 0.5) => color.replace(', 1)', `, ${opacity})`);
6905
6908
  const defaultSettings$1 = Object.freeze({
@@ -6960,6 +6963,96 @@ const createDynamicHistogramData = (values, numberOfBins) => {
6960
6963
  frequencies[numberOfBins - 1] += values.filter(p => p === max).length;
6961
6964
  return { labels, frequencies };
6962
6965
  };
6966
+ /**
6967
+ * Parametric mode: how far either side of the mean the curve is drawn, in standard deviations.
6968
+ * Four covers ~99.99% of the density, so the tails are visually flat by the time it stops.
6969
+ */
6970
+ const curveSpread = 4;
6971
+ const curvePoints = 120;
6972
+ /** Fraction of the drawn range kept clear at each end, so a marker on the edge is not clipped. */
6973
+ const curvePadding = 0.05;
6974
+ const normalPdf = (x, mu, sd) => Math.exp(-0.5 * ((x - mu) / sd) ** 2) / (sd * Math.sqrt(2 * Math.PI));
6975
+ /**
6976
+ * The x range to draw. The reported value is included when it is known, because the whole point of
6977
+ * the chart is to show how far outside the interval it sits — clipping it to the curve would hide
6978
+ * exactly what the reader came to see.
6979
+ */
6980
+ const curveRange = (mu, sd, value, minX) => {
6981
+ const values = [mu - curveSpread * sd, mu + curveSpread * sd, ...(Number.isFinite(value) ? [value] : [])];
6982
+ const from = Math.min(...values);
6983
+ const to = Math.max(...values);
6984
+ const padding = (to - from) * curvePadding;
6985
+ // a fitted normal has mass either side of the mean, but the quantity it describes may not:
6986
+ // drawing a physical amount below its floor invites the reader to think that is possible
6987
+ return { from: Number.isFinite(minX) ? Math.max(from - padding, minX) : from - padding, to: to + padding };
6988
+ };
6989
+ /** Samples the normal PDF, as `{x, y}` points for a linear x axis. */
6990
+ const createNormalCurve = (mu, sd, value, minX) => {
6991
+ const { from, to } = curveRange(mu, sd, value, minX);
6992
+ return Array.from({ length: curvePoints + 1 }, (_, index) => from + ((to - from) * index) / curvePoints).map(x => ({
6993
+ x,
6994
+ y: normalPdf(x, mu, sd)
6995
+ }));
6996
+ };
6997
+ /**
6998
+ * The shaded confidence interval, the mean, and the vertical marker at the reported value.
6999
+ *
7000
+ * The mean earns its line: the y axis is a probability density, whose absolute value means nothing
7001
+ * to a reader, so without it there is no way to tell what the peak of the curve represents.
7002
+ */
7003
+ const distributionAnnotations = (min, max, value, mean) => ({
7004
+ ...(Number.isFinite(min) && Number.isFinite(max)
7005
+ ? {
7006
+ interval: {
7007
+ type: 'box',
7008
+ xMin: min,
7009
+ xMax: max,
7010
+ backgroundColor: opaqueColor(colors.lightBlue, 0.3),
7011
+ borderWidth: 0,
7012
+ drawTime: 'beforeDatasetsDraw'
7013
+ }
7014
+ }
7015
+ : {}),
7016
+ ...(Number.isFinite(mean)
7017
+ ? {
7018
+ mean: {
7019
+ type: 'line',
7020
+ xMin: mean,
7021
+ xMax: mean,
7022
+ borderColor: colors.lightBlue,
7023
+ borderWidth: 1,
7024
+ borderDash: [4, 4],
7025
+ label: {
7026
+ display: true,
7027
+ content: 'average',
7028
+ position: 'start',
7029
+ backgroundColor: colors.lightBlue,
7030
+ color: colors.darkBlue,
7031
+ font: { size: 9 }
7032
+ }
7033
+ }
7034
+ }
7035
+ : {}),
7036
+ ...(Number.isFinite(value)
7037
+ ? {
7038
+ value: {
7039
+ type: 'line',
7040
+ xMin: value,
7041
+ xMax: value,
7042
+ borderColor: colors.orange,
7043
+ borderWidth: 2,
7044
+ label: {
7045
+ display: true,
7046
+ content: `${transform(value, 3, true)}`,
7047
+ position: 'start',
7048
+ backgroundColor: colors.orange,
7049
+ color: '#fff',
7050
+ font: { size: 10 }
7051
+ }
7052
+ }
7053
+ }
7054
+ : {})
7055
+ });
6963
7056
  const createSinglePointDataset = (labels, pointValue) => {
6964
7057
  // Create an array with the same length as the labels, filled with nulls.
6965
7058
  const pointData = new Array(labels.length).fill(null);
@@ -7003,6 +7096,34 @@ class DistributionChartComponent {
7003
7096
  */
7004
7097
  this.maxPercentile = input(0, ...(ngDevMode ? [{ debugName: "maxPercentile" }] : []));
7005
7098
  this.config = input({}, ...(ngDevMode ? [{ debugName: "config" }] : []));
7099
+ /**
7100
+ * Parametric mode: the mean of the distribution. Set `mu` and `sd` to plot the normal curve
7101
+ * instead of binning `distribution` into a histogram — used where only the parameters are known
7102
+ * and there are no samples to bin.
7103
+ */
7104
+ this.mu = input(...(ngDevMode ? [undefined, { debugName: "mu" }] : []));
7105
+ /**
7106
+ * Parametric mode: the standard deviation of the distribution. See `mu`.
7107
+ */
7108
+ this.sd = input(...(ngDevMode ? [undefined, { debugName: "sd" }] : []));
7109
+ /**
7110
+ * Parametric mode: lower bound of the confidence interval to shade.
7111
+ */
7112
+ this.intervalMin = input(...(ngDevMode ? [undefined, { debugName: "intervalMin" }] : []));
7113
+ /**
7114
+ * Parametric mode: upper bound of the confidence interval to shade.
7115
+ */
7116
+ this.intervalMax = input(...(ngDevMode ? [undefined, { debugName: "intervalMax" }] : []));
7117
+ /**
7118
+ * Parametric mode: floor for the drawn range, for a quantity that cannot go below it — pass `0`
7119
+ * for a physical amount. Left unset the curve is drawn symmetrically about `mu`.
7120
+ */
7121
+ this.minX = input(...(ngDevMode ? [undefined, { debugName: "minX" }] : []));
7122
+ /**
7123
+ * `sd` must be strictly positive: a zero or negative deviation makes the PDF a division by zero.
7124
+ */
7125
+ this.parametric = computed(() => Number.isFinite(this.mu()) && Number.isFinite(this.sd()) && this.sd() > 0, ...(ngDevMode ? [{ debugName: "parametric" }] : []));
7126
+ this.curve = computed(() => createNormalCurve(this.mu(), this.sd(), this.value(), this.minX()), ...(ngDevMode ? [{ debugName: "curve" }] : []));
7006
7127
  this.maxPercentileValue = computed(() => this.maxPercentile() ? getPercentileValue(this.distribution(), this.maxPercentile()) : null, ...(ngDevMode ? [{ debugName: "maxPercentileValue" }] : []));
7007
7128
  this.groupedData = computed(() => createDynamicHistogramData(this.maxPercentileValue()
7008
7129
  ? (this.distribution() ?? []).filter(v => v <= this.maxPercentileValue())
@@ -7050,7 +7171,51 @@ class DistributionChartComponent {
7050
7171
  }
7051
7172
  }
7052
7173
  }), ...(ngDevMode ? [{ debugName: "defaultConfig" }] : []));
7053
- this.dataConfig = computed(() => ({
7174
+ /**
7175
+ * The x axis is `linear` here, not the category axis the histogram uses: the curve is `{x, y}`
7176
+ * points, and the annotations position the interval and the marker at real x values.
7177
+ */
7178
+ this.parametricConfig = computed(() => ({
7179
+ options: {
7180
+ plugins: {
7181
+ annotation: {
7182
+ annotations: distributionAnnotations(this.intervalMin(), this.intervalMax(), this.value(), this.mu())
7183
+ }
7184
+ },
7185
+ scales: {
7186
+ x: {
7187
+ type: 'linear',
7188
+ display: true,
7189
+ ticks: {
7190
+ font: {
7191
+ family: defaultTicksFont.family,
7192
+ size: 10,
7193
+ weight: 400
7194
+ },
7195
+ color: '#4A4A4A',
7196
+ callback: value => `${transform(value, 3, true)}`
7197
+ },
7198
+ title: {
7199
+ display: true,
7200
+ text: this.label()
7201
+ }
7202
+ },
7203
+ y: {
7204
+ // the density itself carries no meaning for the reader, only the shape does
7205
+ ticks: {
7206
+ display: false
7207
+ },
7208
+ grid: {
7209
+ display: false
7210
+ },
7211
+ title: {
7212
+ display: false
7213
+ }
7214
+ }
7215
+ }
7216
+ }
7217
+ }), ...(ngDevMode ? [{ debugName: "parametricConfig" }] : []));
7218
+ this.histogramData = computed(() => ({
7054
7219
  datasets: [
7055
7220
  this.distribution()
7056
7221
  ? [
@@ -7089,16 +7254,32 @@ class DistributionChartComponent {
7089
7254
  : []
7090
7255
  ].flat(),
7091
7256
  labels: this.groupedData().labels
7092
- }), ...(ngDevMode ? [{ debugName: "dataConfig" }] : []));
7093
- this.configuration = computed(() => merge$1({}, defaultSettings$1, this.defaultConfig(), this.config()), ...(ngDevMode ? [{ debugName: "configuration" }] : []));
7257
+ }), ...(ngDevMode ? [{ debugName: "histogramData" }] : []));
7258
+ this.parametricData = computed(() => ({
7259
+ datasets: [
7260
+ {
7261
+ label: 'Distribution',
7262
+ data: this.curve(),
7263
+ backgroundColor: opaqueColor(colors.lightBlue, 0.2),
7264
+ // light enough to read on a dark panel, dark enough to read on a white one
7265
+ borderColor: colors.lightBlue,
7266
+ borderWidth: 1,
7267
+ fill: true,
7268
+ pointRadius: 0,
7269
+ type: 'line'
7270
+ }
7271
+ ]
7272
+ }), ...(ngDevMode ? [{ debugName: "parametricData" }] : []));
7273
+ this.dataConfig = computed(() => this.parametric() ? this.parametricData() : this.histogramData(), ...(ngDevMode ? [{ debugName: "dataConfig" }] : []));
7274
+ this.configuration = computed(() => merge$1({}, defaultSettings$1, this.parametric() ? this.parametricConfig() : this.defaultConfig(), this.config()), ...(ngDevMode ? [{ debugName: "configuration" }] : []));
7094
7275
  }
7095
7276
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DistributionChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7096
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: DistributionChartComponent, isStandalone: true, selector: "he-distribution-chart", inputs: { distribution: { classPropertyName: "distribution", publicName: "distribution", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, nbBins: { classPropertyName: "nbBins", publicName: "nbBins", isSignal: true, isRequired: false, transformFunction: null }, maxPercentile: { classPropertyName: "maxPercentile", publicName: "maxPercentile", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, exportAs: ["distributionChart"], ngImport: i0, template: "<he-chart [data]=\"dataConfig()\" [config]=\"configuration()\" />\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "component", type: ChartComponent, selector: "he-chart", inputs: ["data", "config", "showExportButton"], exportAs: ["chart"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7277
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: DistributionChartComponent, isStandalone: true, selector: "he-distribution-chart", inputs: { distribution: { classPropertyName: "distribution", publicName: "distribution", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, nbBins: { classPropertyName: "nbBins", publicName: "nbBins", isSignal: true, isRequired: false, transformFunction: null }, maxPercentile: { classPropertyName: "maxPercentile", publicName: "maxPercentile", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, mu: { classPropertyName: "mu", publicName: "mu", isSignal: true, isRequired: false, transformFunction: null }, sd: { classPropertyName: "sd", publicName: "sd", isSignal: true, isRequired: false, transformFunction: null }, intervalMin: { classPropertyName: "intervalMin", publicName: "intervalMin", isSignal: true, isRequired: false, transformFunction: null }, intervalMax: { classPropertyName: "intervalMax", publicName: "intervalMax", isSignal: true, isRequired: false, transformFunction: null }, minX: { classPropertyName: "minX", publicName: "minX", isSignal: true, isRequired: false, transformFunction: null } }, exportAs: ["distributionChart"], ngImport: i0, template: "<he-chart [data]=\"dataConfig()\" [config]=\"configuration()\" />\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "component", type: ChartComponent, selector: "he-chart", inputs: ["data", "config", "showExportButton"], exportAs: ["chart"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7097
7278
  }
7098
7279
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DistributionChartComponent, decorators: [{
7099
7280
  type: Component$1,
7100
7281
  args: [{ selector: 'he-distribution-chart', exportAs: 'distributionChart', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ChartComponent], template: "<he-chart [data]=\"dataConfig()\" [config]=\"configuration()\" />\n", styles: [":host{display:block}\n"] }]
7101
- }], propDecorators: { distribution: [{ type: i0.Input, args: [{ isSignal: true, alias: "distribution", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], nbBins: [{ type: i0.Input, args: [{ isSignal: true, alias: "nbBins", required: false }] }], maxPercentile: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxPercentile", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }] } });
7282
+ }], propDecorators: { distribution: [{ type: i0.Input, args: [{ isSignal: true, alias: "distribution", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], nbBins: [{ type: i0.Input, args: [{ isSignal: true, alias: "nbBins", required: false }] }], maxPercentile: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxPercentile", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], mu: [{ type: i0.Input, args: [{ isSignal: true, alias: "mu", required: false }] }], sd: [{ type: i0.Input, args: [{ isSignal: true, alias: "sd", required: false }] }], intervalMin: [{ type: i0.Input, args: [{ isSignal: true, alias: "intervalMin", required: false }] }], intervalMax: [{ type: i0.Input, args: [{ isSignal: true, alias: "intervalMax", required: false }] }], minX: [{ type: i0.Input, args: [{ isSignal: true, alias: "minX", required: false }] }] } });
7102
7283
 
7103
7284
  const defaultSettings = Object.freeze({
7104
7285
  type: 'line',
@@ -9130,6 +9311,39 @@ const toTextParts = (value) => {
9130
9311
  return parts;
9131
9312
  };
9132
9313
 
9314
+ /**
9315
+ * A titled block that can be folded away, with room for a control beside its heading.
9316
+ *
9317
+ * Presentation only: it holds no opinion on what it contains, so several of them stacked read as
9318
+ * one document a reader can collapse section by section.
9319
+ */
9320
+ class CollapsibleBlockComponent {
9321
+ constructor() {
9322
+ /**
9323
+ * The label above the content.
9324
+ */
9325
+ this.heading = input('', ...(ngDevMode ? [{ debugName: "heading" }] : []));
9326
+ /**
9327
+ * Whether the block can be folded away. Turn it off and it renders as a plain titled block.
9328
+ */
9329
+ this.collapsible = input(true, ...(ngDevMode ? [{ debugName: "collapsible" }] : []));
9330
+ /**
9331
+ * Whether the content is shown. Two-way, so the caller can fold a block from outside.
9332
+ */
9333
+ this.open = model(true, ...(ngDevMode ? [{ debugName: "open" }] : []));
9334
+ }
9335
+ toggle() {
9336
+ if (this.collapsible())
9337
+ this.open.set(!this.open());
9338
+ }
9339
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: CollapsibleBlockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9340
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: CollapsibleBlockComponent, isStandalone: true, selector: "he-collapsible-block", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, ngImport: i0, template: "<div class=\"collapsible-block is-mb-2\">\n <div class=\"is-flex is-align-items-center is-justify-content-space-between is-gap-8 is-mb-1 is-size-8\">\n <span\n class=\"is-flex is-align-items-center is-gap-4 is-uppercase has-text-weight-semibold\"\n [class.is-clickable]=\"collapsible()\"\n (click)=\"toggle()\">\n @if (collapsible()) {\n <he-svg-icon [name]=\"open() ? 'chevron-down' : 'chevron-right'\" />\n }\n <span>{{ heading() }}</span>\n </span>\n\n <!-- a control that belongs to the block rather than to its content, e.g. a display switch -->\n <ng-content select=\"[blockActions]\" />\n </div>\n\n @if (open()) {\n <ng-content />\n }\n</div>\n", styles: [".collapsible-block{border-bottom:1px solid rgba(255,255,255,.2)}\n"], dependencies: [{ kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9341
+ }
9342
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: CollapsibleBlockComponent, decorators: [{
9343
+ type: Component$1,
9344
+ args: [{ selector: 'he-collapsible-block', changeDetection: ChangeDetectionStrategy.OnPush, imports: [HESvgIconComponent], template: "<div class=\"collapsible-block is-mb-2\">\n <div class=\"is-flex is-align-items-center is-justify-content-space-between is-gap-8 is-mb-1 is-size-8\">\n <span\n class=\"is-flex is-align-items-center is-gap-4 is-uppercase has-text-weight-semibold\"\n [class.is-clickable]=\"collapsible()\"\n (click)=\"toggle()\">\n @if (collapsible()) {\n <he-svg-icon [name]=\"open() ? 'chevron-down' : 'chevron-right'\" />\n }\n <span>{{ heading() }}</span>\n </span>\n\n <!-- a control that belongs to the block rather than to its content, e.g. a display switch -->\n <ng-content select=\"[blockActions]\" />\n </div>\n\n @if (open()) {\n <ng-content />\n }\n</div>\n", styles: [".collapsible-block{border-bottom:1px solid rgba(255,255,255,.2)}\n"] }]
9345
+ }], propDecorators: { heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }] } });
9346
+
9133
9347
  /**
9134
9348
  * Displays a set of formulas with the variables documented under them, and a switch between the
9135
9349
  * symbolic and the substituted view.
@@ -9167,10 +9381,19 @@ class FormulaBlockComponent {
9167
9381
  * several blocks: they share one state, so repeating the switch only repeats the same control.
9168
9382
  */
9169
9383
  this.showToggle = input(true, ...(ngDevMode ? [{ debugName: "showToggle" }] : []));
9384
+ /**
9385
+ * Whether the block can be folded away, so a reader can put aside the part they are not reading.
9386
+ */
9387
+ this.collapsible = input(false, ...(ngDevMode ? [{ debugName: "collapsible" }] : []));
9388
+ /**
9389
+ * Whether the block is unfolded. Two-way, so the caller can fold it from outside.
9390
+ */
9391
+ this.open = model(true, ...(ngDevMode ? [{ debugName: "open" }] : []));
9170
9392
  /**
9171
9393
  * Whether the substituted view is shown. Two-way, so the caller can render accordingly.
9172
9394
  */
9173
9395
  this.substituted = model(false, ...(ngDevMode ? [{ debugName: "substituted" }] : []));
9396
+ this.blockHeading = computed(() => this.heading() || `Formula${this.formulas().length > 1 ? 's' : ''}`, ...(ngDevMode ? [{ debugName: "blockHeading" }] : []));
9174
9397
  // unique per instance so a label only toggles its own checkbox - several can co-exist on a page
9175
9398
  this.toggleId = uuid('formulaSubstituted-');
9176
9399
  /**
@@ -9192,12 +9415,12 @@ class FormulaBlockComponent {
9192
9415
  this.substituted.set(!this.substituted());
9193
9416
  }
9194
9417
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: FormulaBlockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9195
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: FormulaBlockComponent, isStandalone: true, selector: "he-formula-block", inputs: { formulas: { classPropertyName: "formulas", publicName: "formulas", isSignal: true, isRequired: false, transformFunction: null }, hasSubstitutions: { classPropertyName: "hasSubstitutions", publicName: "hasSubstitutions", isSignal: true, isRequired: false, transformFunction: null }, emptyTitle: { classPropertyName: "emptyTitle", publicName: "emptyTitle", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, note: { classPropertyName: "note", publicName: "note", isSignal: true, isRequired: false, transformFunction: null }, showToggle: { classPropertyName: "showToggle", publicName: "showToggle", isSignal: true, isRequired: false, transformFunction: null }, substituted: { classPropertyName: "substituted", publicName: "substituted", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { substituted: "substitutedChange" }, ngImport: i0, template: "@if (items().length) {\n <div class=\"formula-block is-mb-2\">\n <div class=\"is-flex is-align-items-center is-justify-content-space-between is-gap-8 is-mb-1 is-size-8\">\n <span class=\"is-uppercase has-text-weight-semibold\">\n {{ heading() || 'Formula' + (items().length > 1 ? 's' : '') }}\n </span>\n @if (showToggle()) {\n <div class=\"field\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"toggleId\"\n [checked]=\"substituted()\"\n [disabled]=\"!hasSubstitutions()\"\n (change)=\"toggle()\" />\n <label [for]=\"toggleId\" [title]=\"hasSubstitutions() ? '' : emptyTitle()\">\n <span>{{ substituted() ? 'Substituted' : 'Raw' }}</span>\n </label>\n </div>\n }\n </div>\n\n @if (note()) {\n <p class=\"is-size-8 is-mb-1 | formula-note\">{{ note() }}</p>\n }\n\n @for (item of items(); track $index) {\n @if (item.section) {\n <p class=\"is-size-8 is-uppercase has-text-weight-semibold is-mt-2 | formula-section\">{{ item.section }}</p>\n }\n\n @if (item.contextParts.length) {\n <p class=\"is-size-7 is-mt-1 | formula-context\">\n <ng-container *ngTemplateOutlet=\"textParts; context: { parts: item.contextParts }\" />\n </p>\n }\n\n <div class=\"formula\" [heKatex]=\"item.rendered\"></div>\n\n @if (item.variables.length) {\n <ul class=\"is-size-7 is-mt-1 is-mb-2 is-list-style-disc | formula-variables\">\n @for (variable of item.variables; track $index) {\n <li>\n <div class=\"is-flex is-align-items-baseline is-gap-4\">\n <span class=\"formula-variable-symbol is-nowrap\">\n <span [heKatex]=\"variable.symbol\" [heKatexInline]=\"true\"></span>\n @if (variable.descriptionParts.length) {\n <span>:</span>\n }\n </span>\n @if (variable.descriptionParts.length) {\n <span class=\"is-italic | formula-variable-desc\">\n <ng-container *ngTemplateOutlet=\"textParts; context: { parts: variable.descriptionParts }\" />\n </span>\n }\n @if (variable.missing) {\n <span class=\"has-text-warning\">(missing)</span>\n }\n </div>\n </li>\n }\n </ul>\n }\n }\n </div>\n}\n\n<!-- documentation text: the symbols it names render as math, the rest as markdown -->\n<ng-template #textParts let-parts=\"parts\">\n @for (part of parts; track $index) {\n @if (part.math) {\n <span class=\"formula-inline-math\" [heKatex]=\"part.math\" [heKatexInline]=\"true\"></span>\n } @else {\n <markdown class=\"is-inline-block\" [data]=\"part.text\" />\n }\n }\n</ng-template>\n", styles: [".formula-block{border-bottom:1px solid rgba(255,255,255,.2)}.formula-section{opacity:.7;letter-spacing:.03em}.formula-note{opacity:.7}.formula-context{opacity:.9}.formula-variables{padding-inline-start:1rem}.formula-variable-symbol{flex-shrink:0}.formula-variable-symbol ::ng-deep .katex{font-size:1em}.formula-variable-desc{opacity:.85}.formula-context ::ng-deep markdown,.formula-context ::ng-deep markdown *,.formula-variable-desc ::ng-deep markdown,.formula-variable-desc ::ng-deep markdown *{display:inline;margin:0;white-space:pre-wrap}.formula-context .formula-inline-math ::ng-deep .katex,.formula-variable-desc .formula-inline-math ::ng-deep .katex{font-size:1em}.formula{overflow-x:auto;overflow-y:hidden}.formula ::ng-deep .katex-display{margin:.35rem 0}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: KatexDirective, selector: "[heKatex]", inputs: ["heKatex", "heKatexInline"] }, { kind: "component", type: MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9418
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: FormulaBlockComponent, isStandalone: true, selector: "he-formula-block", inputs: { formulas: { classPropertyName: "formulas", publicName: "formulas", isSignal: true, isRequired: false, transformFunction: null }, hasSubstitutions: { classPropertyName: "hasSubstitutions", publicName: "hasSubstitutions", isSignal: true, isRequired: false, transformFunction: null }, emptyTitle: { classPropertyName: "emptyTitle", publicName: "emptyTitle", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, note: { classPropertyName: "note", publicName: "note", isSignal: true, isRequired: false, transformFunction: null }, showToggle: { classPropertyName: "showToggle", publicName: "showToggle", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, substituted: { classPropertyName: "substituted", publicName: "substituted", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", substituted: "substitutedChange" }, ngImport: i0, template: "@if (items().length) {\n <he-collapsible-block [heading]=\"blockHeading()\" [collapsible]=\"collapsible()\" [(open)]=\"open\">\n @if (showToggle()) {\n <div blockActions class=\"field\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"toggleId\"\n [checked]=\"substituted()\"\n [disabled]=\"!hasSubstitutions()\"\n (change)=\"toggle()\" />\n <label [for]=\"toggleId\" [title]=\"hasSubstitutions() ? '' : emptyTitle()\">\n <span>{{ substituted() ? 'Substituted' : 'Raw' }}</span>\n </label>\n </div>\n }\n\n <div class=\"formula-block\">\n @if (note()) {\n <p class=\"is-size-8 is-mb-1 | formula-note\">{{ note() }}</p>\n }\n\n @for (item of items(); track $index) {\n @if (item.section) {\n <p class=\"is-size-8 is-uppercase has-text-weight-semibold is-mt-2 | formula-section\">{{ item.section }}</p>\n }\n\n @if (item.contextParts.length) {\n <p class=\"is-size-7 is-mt-1 | formula-context\">\n <ng-container *ngTemplateOutlet=\"textParts; context: { parts: item.contextParts }\" />\n </p>\n }\n\n <div class=\"formula\" [heKatex]=\"item.rendered\"></div>\n\n @if (item.variables.length) {\n <ul class=\"is-size-7 is-mt-1 is-mb-2 is-list-style-disc | formula-variables\">\n @for (variable of item.variables; track $index) {\n <li>\n <div class=\"is-flex is-align-items-baseline is-gap-4\">\n <span class=\"formula-variable-symbol is-nowrap\">\n <span [heKatex]=\"variable.symbol\" [heKatexInline]=\"true\"></span>\n @if (variable.descriptionParts.length) {\n <span>:</span>\n }\n </span>\n @if (variable.descriptionParts.length) {\n <span class=\"is-italic | formula-variable-desc\">\n <ng-container *ngTemplateOutlet=\"textParts; context: { parts: variable.descriptionParts }\" />\n </span>\n }\n @if (variable.note) {\n <span class=\"formula-variable-note\">({{ variable.note }})</span>\n } @else if (variable.missing) {\n <span class=\"has-text-warning\">(missing)</span>\n }\n </div>\n </li>\n }\n </ul>\n }\n }\n </div>\n </he-collapsible-block>\n}\n\n<!-- documentation text: the symbols it names render as math, the rest as markdown -->\n<ng-template #textParts let-parts=\"parts\">\n @for (part of parts; track $index) {\n @if (part.math) {\n <span class=\"formula-inline-math\" [heKatex]=\"part.math\" [heKatexInline]=\"true\"></span>\n } @else {\n <markdown class=\"is-inline-block\" [data]=\"part.text\" />\n }\n }\n</ng-template>\n", styles: [":host{display:block}.formula-section{opacity:.7;letter-spacing:.03em}.formula-note{opacity:.7}.formula-context{opacity:.9}.formula-variables{padding-inline-start:1rem}.formula-variable-symbol{flex-shrink:0}.formula-variable-symbol ::ng-deep .katex{font-size:1em}.formula-variable-desc{opacity:.85}.formula-variable-note{opacity:.7;white-space:nowrap}.formula-context ::ng-deep markdown,.formula-context ::ng-deep markdown *,.formula-variable-desc ::ng-deep markdown,.formula-variable-desc ::ng-deep markdown *{display:inline;margin:0;white-space:pre-wrap}.formula-context .formula-inline-math ::ng-deep .katex,.formula-variable-desc .formula-inline-math ::ng-deep .katex{font-size:1em}.formula{overflow-x:auto;overflow-y:hidden}.formula ::ng-deep .katex-display{margin:.35rem 0}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: KatexDirective, selector: "[heKatex]", inputs: ["heKatex", "heKatexInline"] }, { kind: "component", type: MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "component", type: CollapsibleBlockComponent, selector: "he-collapsible-block", inputs: ["heading", "collapsible", "open"], outputs: ["openChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9196
9419
  }
9197
9420
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: FormulaBlockComponent, decorators: [{
9198
9421
  type: Component$1,
9199
- args: [{ selector: 'he-formula-block', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet, KatexDirective, MarkdownComponent], template: "@if (items().length) {\n <div class=\"formula-block is-mb-2\">\n <div class=\"is-flex is-align-items-center is-justify-content-space-between is-gap-8 is-mb-1 is-size-8\">\n <span class=\"is-uppercase has-text-weight-semibold\">\n {{ heading() || 'Formula' + (items().length > 1 ? 's' : '') }}\n </span>\n @if (showToggle()) {\n <div class=\"field\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"toggleId\"\n [checked]=\"substituted()\"\n [disabled]=\"!hasSubstitutions()\"\n (change)=\"toggle()\" />\n <label [for]=\"toggleId\" [title]=\"hasSubstitutions() ? '' : emptyTitle()\">\n <span>{{ substituted() ? 'Substituted' : 'Raw' }}</span>\n </label>\n </div>\n }\n </div>\n\n @if (note()) {\n <p class=\"is-size-8 is-mb-1 | formula-note\">{{ note() }}</p>\n }\n\n @for (item of items(); track $index) {\n @if (item.section) {\n <p class=\"is-size-8 is-uppercase has-text-weight-semibold is-mt-2 | formula-section\">{{ item.section }}</p>\n }\n\n @if (item.contextParts.length) {\n <p class=\"is-size-7 is-mt-1 | formula-context\">\n <ng-container *ngTemplateOutlet=\"textParts; context: { parts: item.contextParts }\" />\n </p>\n }\n\n <div class=\"formula\" [heKatex]=\"item.rendered\"></div>\n\n @if (item.variables.length) {\n <ul class=\"is-size-7 is-mt-1 is-mb-2 is-list-style-disc | formula-variables\">\n @for (variable of item.variables; track $index) {\n <li>\n <div class=\"is-flex is-align-items-baseline is-gap-4\">\n <span class=\"formula-variable-symbol is-nowrap\">\n <span [heKatex]=\"variable.symbol\" [heKatexInline]=\"true\"></span>\n @if (variable.descriptionParts.length) {\n <span>:</span>\n }\n </span>\n @if (variable.descriptionParts.length) {\n <span class=\"is-italic | formula-variable-desc\">\n <ng-container *ngTemplateOutlet=\"textParts; context: { parts: variable.descriptionParts }\" />\n </span>\n }\n @if (variable.missing) {\n <span class=\"has-text-warning\">(missing)</span>\n }\n </div>\n </li>\n }\n </ul>\n }\n }\n </div>\n}\n\n<!-- documentation text: the symbols it names render as math, the rest as markdown -->\n<ng-template #textParts let-parts=\"parts\">\n @for (part of parts; track $index) {\n @if (part.math) {\n <span class=\"formula-inline-math\" [heKatex]=\"part.math\" [heKatexInline]=\"true\"></span>\n } @else {\n <markdown class=\"is-inline-block\" [data]=\"part.text\" />\n }\n }\n</ng-template>\n", styles: [".formula-block{border-bottom:1px solid rgba(255,255,255,.2)}.formula-section{opacity:.7;letter-spacing:.03em}.formula-note{opacity:.7}.formula-context{opacity:.9}.formula-variables{padding-inline-start:1rem}.formula-variable-symbol{flex-shrink:0}.formula-variable-symbol ::ng-deep .katex{font-size:1em}.formula-variable-desc{opacity:.85}.formula-context ::ng-deep markdown,.formula-context ::ng-deep markdown *,.formula-variable-desc ::ng-deep markdown,.formula-variable-desc ::ng-deep markdown *{display:inline;margin:0;white-space:pre-wrap}.formula-context .formula-inline-math ::ng-deep .katex,.formula-variable-desc .formula-inline-math ::ng-deep .katex{font-size:1em}.formula{overflow-x:auto;overflow-y:hidden}.formula ::ng-deep .katex-display{margin:.35rem 0}\n"] }]
9200
- }], propDecorators: { formulas: [{ type: i0.Input, args: [{ isSignal: true, alias: "formulas", required: false }] }], hasSubstitutions: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasSubstitutions", required: false }] }], emptyTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyTitle", required: false }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], note: [{ type: i0.Input, args: [{ isSignal: true, alias: "note", required: false }] }], showToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToggle", required: false }] }], substituted: [{ type: i0.Input, args: [{ isSignal: true, alias: "substituted", required: false }] }, { type: i0.Output, args: ["substitutedChange"] }] } });
9422
+ args: [{ selector: 'he-formula-block', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet, KatexDirective, MarkdownComponent, CollapsibleBlockComponent], template: "@if (items().length) {\n <he-collapsible-block [heading]=\"blockHeading()\" [collapsible]=\"collapsible()\" [(open)]=\"open\">\n @if (showToggle()) {\n <div blockActions class=\"field\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"toggleId\"\n [checked]=\"substituted()\"\n [disabled]=\"!hasSubstitutions()\"\n (change)=\"toggle()\" />\n <label [for]=\"toggleId\" [title]=\"hasSubstitutions() ? '' : emptyTitle()\">\n <span>{{ substituted() ? 'Substituted' : 'Raw' }}</span>\n </label>\n </div>\n }\n\n <div class=\"formula-block\">\n @if (note()) {\n <p class=\"is-size-8 is-mb-1 | formula-note\">{{ note() }}</p>\n }\n\n @for (item of items(); track $index) {\n @if (item.section) {\n <p class=\"is-size-8 is-uppercase has-text-weight-semibold is-mt-2 | formula-section\">{{ item.section }}</p>\n }\n\n @if (item.contextParts.length) {\n <p class=\"is-size-7 is-mt-1 | formula-context\">\n <ng-container *ngTemplateOutlet=\"textParts; context: { parts: item.contextParts }\" />\n </p>\n }\n\n <div class=\"formula\" [heKatex]=\"item.rendered\"></div>\n\n @if (item.variables.length) {\n <ul class=\"is-size-7 is-mt-1 is-mb-2 is-list-style-disc | formula-variables\">\n @for (variable of item.variables; track $index) {\n <li>\n <div class=\"is-flex is-align-items-baseline is-gap-4\">\n <span class=\"formula-variable-symbol is-nowrap\">\n <span [heKatex]=\"variable.symbol\" [heKatexInline]=\"true\"></span>\n @if (variable.descriptionParts.length) {\n <span>:</span>\n }\n </span>\n @if (variable.descriptionParts.length) {\n <span class=\"is-italic | formula-variable-desc\">\n <ng-container *ngTemplateOutlet=\"textParts; context: { parts: variable.descriptionParts }\" />\n </span>\n }\n @if (variable.note) {\n <span class=\"formula-variable-note\">({{ variable.note }})</span>\n } @else if (variable.missing) {\n <span class=\"has-text-warning\">(missing)</span>\n }\n </div>\n </li>\n }\n </ul>\n }\n }\n </div>\n </he-collapsible-block>\n}\n\n<!-- documentation text: the symbols it names render as math, the rest as markdown -->\n<ng-template #textParts let-parts=\"parts\">\n @for (part of parts; track $index) {\n @if (part.math) {\n <span class=\"formula-inline-math\" [heKatex]=\"part.math\" [heKatexInline]=\"true\"></span>\n } @else {\n <markdown class=\"is-inline-block\" [data]=\"part.text\" />\n }\n }\n</ng-template>\n", styles: [":host{display:block}.formula-section{opacity:.7;letter-spacing:.03em}.formula-note{opacity:.7}.formula-context{opacity:.9}.formula-variables{padding-inline-start:1rem}.formula-variable-symbol{flex-shrink:0}.formula-variable-symbol ::ng-deep .katex{font-size:1em}.formula-variable-desc{opacity:.85}.formula-variable-note{opacity:.7;white-space:nowrap}.formula-context ::ng-deep markdown,.formula-context ::ng-deep markdown *,.formula-variable-desc ::ng-deep markdown,.formula-variable-desc ::ng-deep markdown *{display:inline;margin:0;white-space:pre-wrap}.formula-context .formula-inline-math ::ng-deep .katex,.formula-variable-desc .formula-inline-math ::ng-deep .katex{font-size:1em}.formula{overflow-x:auto;overflow-y:hidden}.formula ::ng-deep .katex-display{margin:.35rem 0}\n"] }]
9423
+ }], propDecorators: { formulas: [{ type: i0.Input, args: [{ isSignal: true, alias: "formulas", required: false }] }], hasSubstitutions: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasSubstitutions", required: false }] }], emptyTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyTitle", required: false }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], note: [{ type: i0.Input, args: [{ isSignal: true, alias: "note", required: false }] }], showToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToggle", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], substituted: [{ type: i0.Input, args: [{ isSignal: true, alias: "substituted", required: false }] }, { type: i0.Output, args: ["substitutedChange"] }] } });
9201
9424
 
9202
9425
  const isMissing$1 = (value) => value === undefined || value === null || value === '';
9203
9426
  // the result symbol (left-hand side) - it is the model's output, not an input that "was not logged", so
@@ -9350,7 +9573,7 @@ class NodeLogsModelsFormulaComponent {
9350
9573
  return omitKeys(values, omit);
9351
9574
  }
9352
9575
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeLogsModelsFormulaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9353
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: NodeLogsModelsFormulaComponent, isStandalone: true, selector: "he-node-logs-models-formula", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: false, transformFunction: null }, logs: { classPropertyName: "logs", publicName: "logs", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: false, transformFunction: null }, substituted: { classPropertyName: "substituted", publicName: "substituted", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { substituted: "substitutedChange" }, ngImport: i0, template: "<he-formula-block\n [formulas]=\"renderedFormulas()\"\n [hasSubstitutions]=\"hasSubstitutions()\"\n [(substituted)]=\"substituted\" />\n", dependencies: [{ kind: "component", type: FormulaBlockComponent, selector: "he-formula-block", inputs: ["formulas", "hasSubstitutions", "emptyTitle", "heading", "note", "showToggle", "substituted"], outputs: ["substitutedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9576
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: NodeLogsModelsFormulaComponent, isStandalone: true, selector: "he-node-logs-models-formula", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: false, transformFunction: null }, logs: { classPropertyName: "logs", publicName: "logs", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: false, transformFunction: null }, substituted: { classPropertyName: "substituted", publicName: "substituted", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { substituted: "substitutedChange" }, ngImport: i0, template: "<he-formula-block\n [formulas]=\"renderedFormulas()\"\n [hasSubstitutions]=\"hasSubstitutions()\"\n [(substituted)]=\"substituted\" />\n", dependencies: [{ kind: "component", type: FormulaBlockComponent, selector: "he-formula-block", inputs: ["formulas", "hasSubstitutions", "emptyTitle", "heading", "note", "showToggle", "collapsible", "open", "substituted"], outputs: ["openChange", "substitutedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9354
9577
  }
9355
9578
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeLogsModelsFormulaComponent, decorators: [{
9356
9579
  type: Component$1,
@@ -11549,7 +11772,7 @@ class NodeValueDetailsComponent {
11549
11772
  this.updateKeys(selectedKeys);
11550
11773
  }
11551
11774
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeValueDetailsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11552
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeValueDetailsComponent, isStandalone: true, selector: "he-node-value-details", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, nodeType: { classPropertyName: "nodeType", publicName: "nodeType", isSignal: true, isRequired: true, transformFunction: null }, dataState: { classPropertyName: "dataState", publicName: "dataState", isSignal: true, isRequired: true, transformFunction: null }, dataKey: { classPropertyName: "dataKey", publicName: "dataKey", isSignal: true, isRequired: true, transformFunction: null }, aggregated: { classPropertyName: "aggregated", publicName: "aggregated", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (showInline()) {\n @for (key of keys; track key) {\n <he-link-key-value\n [node]=\"node()\"\n [nodeType]=\"nodeType()\"\n [dataState]=\"dataState()\"\n [dataKey]=\"dataKey()\"\n [key]=\"key\" />\n }\n @for (key of additionalKeys; track key) {\n <he-link-key-value\n [node]=\"node()\"\n [nodeType]=\"nodeType()\"\n [dataState]=\"dataState()\"\n [dataKey]=\"dataKey()\"\n [key]=\"key\" />\n }\n\n <ng-container *ngTemplateOutlet=\"showModels\" />\n\n @if (node().distribution?.length) {\n <p>\n <a class=\"is-dark\" [href]=\"schemaBaseUrl + '/' + node['@type'] + '#distribution'\" target=\"_blank\">\n <b>distribution</b>\n </a>\n <span class=\"pr-2\">:</span>\n <ng-container *ngTemplateOutlet=\"distributionContent; context: { node: node() }\" />\n </p>\n }\n} @else {\n <he-link-key-value\n [node]=\"node()\"\n [nodeType]=\"nodeType()\"\n [dataState]=\"dataState()\"\n [dataKey]=\"dataKey()\"\n key=\"term\" />\n\n <ng-container *ngTemplateOutlet=\"showModels\" />\n\n <div class=\"columns is-p-0 is-my-0 is-overflow-visible\">\n <div class=\"column is-p-0 is-my-0\"></div>\n <div class=\"column is-p-0 is-my-0 is-narrow is-overflow-visible\">\n <div ngbDropdown class=\"is-overflow-visible\" autoClose=\"outside\" placement=\"bottom-end\">\n <button\n ngbDropdownToggle\n class=\"button is-small is-ghost has-text-white\"\n type=\"button\"\n aria-controls=\"config-menu\">\n <span>Customise fields</span>\n <span class=\"icon is-small\">\n <he-svg-icon name=\"settings\" aria-hidden=\"true\" />\n </span>\n </button>\n\n <div ngbDropdownMenu id=\"config-menu\">\n <div\n class=\"dropdown-content is-overflow-y-auto\"\n (click)=\"$event.stopPropagation()\"\n cdkDropList\n (cdkDropListDropped)=\"dropTableKey($event)\">\n @for (key of tableKeys(); track key.key; let keyIndex = $index) {\n <div class=\"dropdown-item cdk-drag-item\" cdkDrag>\n <div class=\"field is-relative\">\n <input\n type=\"checkbox\"\n class=\"selector\"\n [id]=\"key.key\"\n [name]=\"key.key\"\n [checked]=\"key.selected\"\n (change)=\"onTableKeyChange(key, keyIndex, $event.target.checked)\" />\n <label class=\"is-pl-2\" [for]=\"key.key\">{{ key.key }}</label>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"table-container is-mt-2\">\n <table class=\"table is-dark is-narrow is-striped\">\n <thead>\n @for (key of visibleTableKeys(); track key) {\n <th>\n <a class=\"is-dark\" [href]=\"schemaBaseUrl + '/' + type() + '#' + key.split('.')[0]\" target=\"_blank\">\n <b>{{ key.includes('.') ? key.split('.')[1] : key }}</b>\n </a>\n </th>\n }\n </thead>\n <tbody>\n @for (node of nodes(); track node) {\n <tr>\n @for (key of visibleTableKeys(); track key) {\n <td>\n @if (key === 'distribution') {\n <ng-container *ngTemplateOutlet=\"distributionContent; context: { node }\" />\n } @else {\n <he-link-key-value\n [node]=\"node\"\n [nodeType]=\"nodeType()\"\n [dataState]=\"dataState()\"\n [dataKey]=\"dataKey()\"\n [key]=\"key\"\n [defaultValue]=\"defaultValue(key)\" />\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n}\n\n<ng-template #distributionContent let-node=\"node\">\n @if (node.distribution?.length) {\n @if (showDistribution() === node) {\n <a class=\"has-text-white\" (click)=\"showDistribution.set(undefined)\">Hide</a>\n } @else {\n <a class=\"has-text-white\" (click)=\"showDistribution.set(node)\">Show</a>\n }\n } @else {\n <span>N/A</span>\n }\n</ng-template>\n\n@if (chartDistribution()) {\n <div class=\"has-background-white is-mt-2 is-p-2 is-rounded | chart-container\">\n <he-distribution-chart\n [distribution]=\"chartDistribution()\"\n [value]=\"chartValue()\"\n [label]=\"chartLabel()\"\n [nbBins]=\"10\"\n [maxPercentile]=\"0.99\" />\n </div>\n}\n\n<ng-template #showModels>\n @if (models().length) {\n <p>\n <span class=\"is-inline-block\">\n <b>possible models used</b>\n </span>\n <span class=\"pr-2\">:</span>\n @for (model of models(); track model; let lastModel = $last) {\n <a class=\"is-dark\" [href]=\"model.link\">{{ model.name }}</a>\n @if (!lastModel) {\n <span class=\"is-pr-1 is-inline-block\">;</span>\n }\n }\n </p>\n }\n</ng-template>\n", styles: ["table{background-color:transparent}table::ng-deep he-link-key-value>a:first-child,table::ng-deep he-link-key-value>a:first-child+span{display:none}.dropdown-content{max-height:300px}.cdk-drag-preview{box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;list-style:none}.cdk-drag-placeholder{opacity:0}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.cdk-drag-item{cursor:move}.chart-container{border-radius:3px}.chart-container he-distribution-chart{height:250px;min-width:400px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "ngmodule", type: NgbTooltipModule }, { kind: "ngmodule", type: NgbDropdownModule }, { kind: "directive", type: i1$1.NgbDropdown, selector: "[ngbDropdown]", inputs: ["autoClose", "dropdownClass", "open", "placement", "popperOptions", "container", "display"], outputs: ["openChange"], exportAs: ["ngbDropdown"] }, { kind: "directive", type: i1$1.NgbDropdownToggle, selector: "[ngbDropdownToggle]" }, { kind: "directive", type: i1$1.NgbDropdownMenu, selector: "[ngbDropdownMenu]" }, { kind: "component", type: LinkKeyValueComponent, selector: "he-link-key-value", inputs: ["node", "nodeType", "dataState", "dataKey", "key", "defaultValue"] }, { kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "component", type: DistributionChartComponent, selector: "he-distribution-chart", inputs: ["distribution", "value", "label", "nbBins", "maxPercentile", "config"], exportAs: ["distributionChart"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
11775
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeValueDetailsComponent, isStandalone: true, selector: "he-node-value-details", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, nodeType: { classPropertyName: "nodeType", publicName: "nodeType", isSignal: true, isRequired: true, transformFunction: null }, dataState: { classPropertyName: "dataState", publicName: "dataState", isSignal: true, isRequired: true, transformFunction: null }, dataKey: { classPropertyName: "dataKey", publicName: "dataKey", isSignal: true, isRequired: true, transformFunction: null }, aggregated: { classPropertyName: "aggregated", publicName: "aggregated", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (showInline()) {\n @for (key of keys; track key) {\n <he-link-key-value\n [node]=\"node()\"\n [nodeType]=\"nodeType()\"\n [dataState]=\"dataState()\"\n [dataKey]=\"dataKey()\"\n [key]=\"key\" />\n }\n @for (key of additionalKeys; track key) {\n <he-link-key-value\n [node]=\"node()\"\n [nodeType]=\"nodeType()\"\n [dataState]=\"dataState()\"\n [dataKey]=\"dataKey()\"\n [key]=\"key\" />\n }\n\n <ng-container *ngTemplateOutlet=\"showModels\" />\n\n @if (node().distribution?.length) {\n <p>\n <a class=\"is-dark\" [href]=\"schemaBaseUrl + '/' + node['@type'] + '#distribution'\" target=\"_blank\">\n <b>distribution</b>\n </a>\n <span class=\"pr-2\">:</span>\n <ng-container *ngTemplateOutlet=\"distributionContent; context: { node: node() }\" />\n </p>\n }\n} @else {\n <he-link-key-value\n [node]=\"node()\"\n [nodeType]=\"nodeType()\"\n [dataState]=\"dataState()\"\n [dataKey]=\"dataKey()\"\n key=\"term\" />\n\n <ng-container *ngTemplateOutlet=\"showModels\" />\n\n <div class=\"columns is-p-0 is-my-0 is-overflow-visible\">\n <div class=\"column is-p-0 is-my-0\"></div>\n <div class=\"column is-p-0 is-my-0 is-narrow is-overflow-visible\">\n <div ngbDropdown class=\"is-overflow-visible\" autoClose=\"outside\" placement=\"bottom-end\">\n <button\n ngbDropdownToggle\n class=\"button is-small is-ghost has-text-white\"\n type=\"button\"\n aria-controls=\"config-menu\">\n <span>Customise fields</span>\n <span class=\"icon is-small\">\n <he-svg-icon name=\"settings\" aria-hidden=\"true\" />\n </span>\n </button>\n\n <div ngbDropdownMenu id=\"config-menu\">\n <div\n class=\"dropdown-content is-overflow-y-auto\"\n (click)=\"$event.stopPropagation()\"\n cdkDropList\n (cdkDropListDropped)=\"dropTableKey($event)\">\n @for (key of tableKeys(); track key.key; let keyIndex = $index) {\n <div class=\"dropdown-item cdk-drag-item\" cdkDrag>\n <div class=\"field is-relative\">\n <input\n type=\"checkbox\"\n class=\"selector\"\n [id]=\"key.key\"\n [name]=\"key.key\"\n [checked]=\"key.selected\"\n (change)=\"onTableKeyChange(key, keyIndex, $event.target.checked)\" />\n <label class=\"is-pl-2\" [for]=\"key.key\">{{ key.key }}</label>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"table-container is-mt-2\">\n <table class=\"table is-dark is-narrow is-striped\">\n <thead>\n @for (key of visibleTableKeys(); track key) {\n <th>\n <a class=\"is-dark\" [href]=\"schemaBaseUrl + '/' + type() + '#' + key.split('.')[0]\" target=\"_blank\">\n <b>{{ key.includes('.') ? key.split('.')[1] : key }}</b>\n </a>\n </th>\n }\n </thead>\n <tbody>\n @for (node of nodes(); track node) {\n <tr>\n @for (key of visibleTableKeys(); track key) {\n <td>\n @if (key === 'distribution') {\n <ng-container *ngTemplateOutlet=\"distributionContent; context: { node }\" />\n } @else {\n <he-link-key-value\n [node]=\"node\"\n [nodeType]=\"nodeType()\"\n [dataState]=\"dataState()\"\n [dataKey]=\"dataKey()\"\n [key]=\"key\"\n [defaultValue]=\"defaultValue(key)\" />\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n}\n\n<ng-template #distributionContent let-node=\"node\">\n @if (node.distribution?.length) {\n @if (showDistribution() === node) {\n <a class=\"has-text-white\" (click)=\"showDistribution.set(undefined)\">Hide</a>\n } @else {\n <a class=\"has-text-white\" (click)=\"showDistribution.set(node)\">Show</a>\n }\n } @else {\n <span>N/A</span>\n }\n</ng-template>\n\n@if (chartDistribution()) {\n <div class=\"has-background-white is-mt-2 is-p-2 is-rounded | chart-container\">\n <he-distribution-chart\n [distribution]=\"chartDistribution()\"\n [value]=\"chartValue()\"\n [label]=\"chartLabel()\"\n [nbBins]=\"10\"\n [maxPercentile]=\"0.99\" />\n </div>\n}\n\n<ng-template #showModels>\n @if (models().length) {\n <p>\n <span class=\"is-inline-block\">\n <b>possible models used</b>\n </span>\n <span class=\"pr-2\">:</span>\n @for (model of models(); track model; let lastModel = $last) {\n <a class=\"is-dark\" [href]=\"model.link\">{{ model.name }}</a>\n @if (!lastModel) {\n <span class=\"is-pr-1 is-inline-block\">;</span>\n }\n }\n </p>\n }\n</ng-template>\n", styles: ["table{background-color:transparent}table::ng-deep he-link-key-value>a:first-child,table::ng-deep he-link-key-value>a:first-child+span{display:none}.dropdown-content{max-height:300px}.cdk-drag-preview{box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;list-style:none}.cdk-drag-placeholder{opacity:0}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.cdk-drag-item{cursor:move}.chart-container{border-radius:3px}.chart-container he-distribution-chart{height:250px;min-width:400px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "ngmodule", type: NgbTooltipModule }, { kind: "ngmodule", type: NgbDropdownModule }, { kind: "directive", type: i1$1.NgbDropdown, selector: "[ngbDropdown]", inputs: ["autoClose", "dropdownClass", "open", "placement", "popperOptions", "container", "display"], outputs: ["openChange"], exportAs: ["ngbDropdown"] }, { kind: "directive", type: i1$1.NgbDropdownToggle, selector: "[ngbDropdownToggle]" }, { kind: "directive", type: i1$1.NgbDropdownMenu, selector: "[ngbDropdownMenu]" }, { kind: "component", type: LinkKeyValueComponent, selector: "he-link-key-value", inputs: ["node", "nodeType", "dataState", "dataKey", "key", "defaultValue"] }, { kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "component", type: DistributionChartComponent, selector: "he-distribution-chart", inputs: ["distribution", "value", "label", "nbBins", "maxPercentile", "config", "mu", "sd", "intervalMin", "intervalMax", "minX"], exportAs: ["distributionChart"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
11553
11776
  }
11554
11777
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeValueDetailsComponent, decorators: [{
11555
11778
  type: Component$1,
@@ -11565,6 +11788,125 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
11565
11788
  ], template: "@if (showInline()) {\n @for (key of keys; track key) {\n <he-link-key-value\n [node]=\"node()\"\n [nodeType]=\"nodeType()\"\n [dataState]=\"dataState()\"\n [dataKey]=\"dataKey()\"\n [key]=\"key\" />\n }\n @for (key of additionalKeys; track key) {\n <he-link-key-value\n [node]=\"node()\"\n [nodeType]=\"nodeType()\"\n [dataState]=\"dataState()\"\n [dataKey]=\"dataKey()\"\n [key]=\"key\" />\n }\n\n <ng-container *ngTemplateOutlet=\"showModels\" />\n\n @if (node().distribution?.length) {\n <p>\n <a class=\"is-dark\" [href]=\"schemaBaseUrl + '/' + node['@type'] + '#distribution'\" target=\"_blank\">\n <b>distribution</b>\n </a>\n <span class=\"pr-2\">:</span>\n <ng-container *ngTemplateOutlet=\"distributionContent; context: { node: node() }\" />\n </p>\n }\n} @else {\n <he-link-key-value\n [node]=\"node()\"\n [nodeType]=\"nodeType()\"\n [dataState]=\"dataState()\"\n [dataKey]=\"dataKey()\"\n key=\"term\" />\n\n <ng-container *ngTemplateOutlet=\"showModels\" />\n\n <div class=\"columns is-p-0 is-my-0 is-overflow-visible\">\n <div class=\"column is-p-0 is-my-0\"></div>\n <div class=\"column is-p-0 is-my-0 is-narrow is-overflow-visible\">\n <div ngbDropdown class=\"is-overflow-visible\" autoClose=\"outside\" placement=\"bottom-end\">\n <button\n ngbDropdownToggle\n class=\"button is-small is-ghost has-text-white\"\n type=\"button\"\n aria-controls=\"config-menu\">\n <span>Customise fields</span>\n <span class=\"icon is-small\">\n <he-svg-icon name=\"settings\" aria-hidden=\"true\" />\n </span>\n </button>\n\n <div ngbDropdownMenu id=\"config-menu\">\n <div\n class=\"dropdown-content is-overflow-y-auto\"\n (click)=\"$event.stopPropagation()\"\n cdkDropList\n (cdkDropListDropped)=\"dropTableKey($event)\">\n @for (key of tableKeys(); track key.key; let keyIndex = $index) {\n <div class=\"dropdown-item cdk-drag-item\" cdkDrag>\n <div class=\"field is-relative\">\n <input\n type=\"checkbox\"\n class=\"selector\"\n [id]=\"key.key\"\n [name]=\"key.key\"\n [checked]=\"key.selected\"\n (change)=\"onTableKeyChange(key, keyIndex, $event.target.checked)\" />\n <label class=\"is-pl-2\" [for]=\"key.key\">{{ key.key }}</label>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"table-container is-mt-2\">\n <table class=\"table is-dark is-narrow is-striped\">\n <thead>\n @for (key of visibleTableKeys(); track key) {\n <th>\n <a class=\"is-dark\" [href]=\"schemaBaseUrl + '/' + type() + '#' + key.split('.')[0]\" target=\"_blank\">\n <b>{{ key.includes('.') ? key.split('.')[1] : key }}</b>\n </a>\n </th>\n }\n </thead>\n <tbody>\n @for (node of nodes(); track node) {\n <tr>\n @for (key of visibleTableKeys(); track key) {\n <td>\n @if (key === 'distribution') {\n <ng-container *ngTemplateOutlet=\"distributionContent; context: { node }\" />\n } @else {\n <he-link-key-value\n [node]=\"node\"\n [nodeType]=\"nodeType()\"\n [dataState]=\"dataState()\"\n [dataKey]=\"dataKey()\"\n [key]=\"key\"\n [defaultValue]=\"defaultValue(key)\" />\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n}\n\n<ng-template #distributionContent let-node=\"node\">\n @if (node.distribution?.length) {\n @if (showDistribution() === node) {\n <a class=\"has-text-white\" (click)=\"showDistribution.set(undefined)\">Hide</a>\n } @else {\n <a class=\"has-text-white\" (click)=\"showDistribution.set(node)\">Show</a>\n }\n } @else {\n <span>N/A</span>\n }\n</ng-template>\n\n@if (chartDistribution()) {\n <div class=\"has-background-white is-mt-2 is-p-2 is-rounded | chart-container\">\n <he-distribution-chart\n [distribution]=\"chartDistribution()\"\n [value]=\"chartValue()\"\n [label]=\"chartLabel()\"\n [nbBins]=\"10\"\n [maxPercentile]=\"0.99\" />\n </div>\n}\n\n<ng-template #showModels>\n @if (models().length) {\n <p>\n <span class=\"is-inline-block\">\n <b>possible models used</b>\n </span>\n <span class=\"pr-2\">:</span>\n @for (model of models(); track model; let lastModel = $last) {\n <a class=\"is-dark\" [href]=\"model.link\">{{ model.name }}</a>\n @if (!lastModel) {\n <span class=\"is-pr-1 is-inline-block\">;</span>\n }\n }\n </p>\n }\n</ng-template>\n", styles: ["table{background-color:transparent}table::ng-deep he-link-key-value>a:first-child,table::ng-deep he-link-key-value>a:first-child+span{display:none}.dropdown-content{max-height:300px}.cdk-drag-preview{box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;list-style:none}.cdk-drag-placeholder{opacity:0}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.cdk-drag-item{cursor:move}.chart-container{border-radius:3px}.chart-container he-distribution-chart{height:250px;min-width:400px}\n"] }]
11566
11789
  }], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }], nodeType: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeType", required: true }] }], dataState: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataState", required: true }] }], dataKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataKey", required: true }] }], aggregated: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregated", required: false }] }] } });
11567
11790
 
11791
+ const isMissing = (value) => value === undefined || value === null || value === '';
11792
+ /**
11793
+ * The quantities only a weighting rule binds, so a rule is placed by what it computes rather than
11794
+ * by where the documentation happens to write it. The weight normalisation stores none of its
11795
+ * symbols, so it is named by the one it defines.
11796
+ */
11797
+ const WEIGHT_KEYS = [
11798
+ 'organic_factor',
11799
+ 'organic_weight_lookup_value',
11800
+ 'irrigated_area',
11801
+ 'plantation_lifespan',
11802
+ 'world_production'
11803
+ ];
11804
+ const WEIGHT_SYMBOLS = ['\\hat{w}_i'];
11805
+ const formulaGroup = (formula) => formula.bindings.some(({ key, symbol }) => WEIGHT_KEYS.includes(key ?? '') || WEIGHT_SYMBOLS.includes(symbol ?? ''))
11806
+ ? 'weights'
11807
+ : 'value';
11808
+ /**
11809
+ * A symbol that should carry a value: the result and the inputs, but never a fixed constant or a
11810
+ * quantity the aggregation deliberately does not store.
11811
+ */
11812
+ const isSubstitutable = (binding) => !!binding.key && binding.constant === undefined && !binding.display;
11813
+ /**
11814
+ * The number of rows a table would have had, which the aggregation records under `<table>_count`
11815
+ * when there were too many contributors to name every one of them.
11816
+ */
11817
+ const tableCount = (values, key) => {
11818
+ const count = key ? values[`${key}_count`] : undefined;
11819
+ return typeof count === 'number' && count > 0 ? count : undefined;
11820
+ };
11821
+ const rowCount = (values, binding) => binding.column ? tableCount(values, binding.key) : undefined;
11822
+ /**
11823
+ * How a symbol that did not resolve is explained: the contributors were not recorded one by one,
11824
+ * only counted - which is a different thing from a value the aggregation never logged.
11825
+ */
11826
+ const notListedNote = (values, binding) => {
11827
+ const count = rowCount(values, binding);
11828
+ return isSubstitutable(binding) && isMissing(values[binding.key]) && count
11829
+ ? `${count} Cycles, not listed`
11830
+ : undefined;
11831
+ };
11832
+ /**
11833
+ * Whether a symbol that should have resolved did not. A symbol whose contributors were counted
11834
+ * rather than listed is not missing: `notListedNote` says how many there were instead.
11835
+ */
11836
+ const isMissingValue = (values, binding) => isSubstitutable(binding) && isMissing(values[binding.key]) && !rowCount(values, binding);
11837
+ /**
11838
+ * Whether any symbol of these formulas resolves. When none does, the substituted view would be
11839
+ * identical to the symbolic one.
11840
+ */
11841
+ const hasAnySubstitution = (values, bindings) => bindings.some(binding => isSubstitutable(binding) && !isMissing(values[binding.key]));
11842
+ // a symbol reading one column of a contributors table: `key` names the table, `column` the field
11843
+ const isTableBinding = (binding) => !!binding.key && !!binding.column && !binding.match;
11844
+ // the packed `log_as_table` string: rows split on `;`, columns on `_`, each column a `key:value`
11845
+ const parseRows = (packed) => typeof packed !== 'string'
11846
+ ? []
11847
+ : packed
11848
+ .split(';')
11849
+ .filter(Boolean)
11850
+ .map(row => row.split('_').reduce((columns, pair) => {
11851
+ const at = pair.indexOf(':');
11852
+ return at === -1 ? columns : { ...columns, [pair.slice(0, at)]: pair.slice(at + 1) };
11853
+ }, {}));
11854
+ const ID_COLUMN$1 = 'id';
11855
+ // how many leading segments every id shares, e.g. the product and country of a sub-aggregation
11856
+ const commonPrefix = (ids) => {
11857
+ const [first = [], ...rest] = ids;
11858
+ const shared = first.findIndex((segment, index) => rest.some(other => other[index] !== segment));
11859
+ return shared === -1 ? first.length : shared;
11860
+ };
11861
+ /**
11862
+ * How many trailing segments every id shares, counting only the period and run date they are all
11863
+ * stamped with. Stopping at the first word is what keeps a compound apart from what it is compared
11864
+ * against: `irrigated` and `non-irrigated` share a word, and dropping it would lose the difference.
11865
+ */
11866
+ const commonPeriod = (ids) => {
11867
+ const [first = [], ...rest] = ids;
11868
+ const shared = [...first]
11869
+ .reverse()
11870
+ .findIndex((segment, index) => !/^\d+$/.test(segment) || rest.some(other => other[other.length - 1 - index] !== segment));
11871
+ return shared === -1 ? first.length : shared;
11872
+ };
11873
+ /**
11874
+ * The part of each id that tells the contributors apart, dropping what they all share - the
11875
+ * product, country and period, which the Cycle being read already names. Falls back to the full id
11876
+ * where that leaves nothing, so a row is never unlabelled.
11877
+ */
11878
+ const distinguishingLabels = (ids) => {
11879
+ const segments = ids.map(id => id.split('-'));
11880
+ const start = commonPrefix(segments);
11881
+ const end = commonPeriod(segments);
11882
+ return ids.map((id, index) => segments[index].slice(start, segments[index].length - end).join('-') || id);
11883
+ };
11884
+ /**
11885
+ * The contributors table these formulas read from: the first one the aggregation recorded, since
11886
+ * they all list the same Cycles - the zero-filled table is the wider of the two.
11887
+ *
11888
+ * Undefined when nothing was recorded, which is every aggregation that logs no values at all.
11889
+ */
11890
+ const contributorsTable = (values, formulas) => {
11891
+ const bindings = formulas.flatMap(formula => formula.bindings.filter(isTableBinding));
11892
+ const key = bindings
11893
+ .map(binding => binding.key)
11894
+ .find(name => !isMissing(values[name]) || tableCount(values, name));
11895
+ if (!key)
11896
+ return undefined;
11897
+ const columns = bindings
11898
+ .filter(binding => binding.key === key)
11899
+ .map(({ symbol, description, column }) => ({ symbol, description, column: column }));
11900
+ const parsed = parseRows(values[key]);
11901
+ const labels = distinguishingLabels(parsed.map(row => row[ID_COLUMN$1]));
11902
+ const rows = parsed.map((row, index) => ({
11903
+ id: row[ID_COLUMN$1],
11904
+ label: labels[index],
11905
+ values: columns.map(({ column }) => row[column])
11906
+ }));
11907
+ return { columns, rows, count: tableCount(values, key) };
11908
+ };
11909
+
11568
11910
  // The page every aggregation follows, whatever the product.
11569
11911
  const GENERAL_PAGE = 'general-process';
11570
11912
  // The product-specific page, by the primary product's `termType`. A term type with no page of its
@@ -11573,16 +11915,17 @@ const PRODUCT_PAGES = {
11573
11915
  crop: 'crop',
11574
11916
  processedFood: 'processed-food'
11575
11917
  };
11576
- // How each page's rules relate to the others, so a reader told "here are two sets of formulas" knows
11577
- // which is which. Named after the guide pages they come from, which the row's guide button opens.
11578
- const PAGE_HEADINGS = {
11579
- 'general-process': { heading: 'General rules', applies: 'Applied to every aggregation, whatever the product.' },
11580
- crop: { heading: 'Crop aggregation', applies: 'Applied on top of the general rules, for crop products.' },
11581
- 'processed-food': {
11582
- heading: 'Processed food aggregation',
11583
- applies: 'Applied on top of the general rules, for processed food.'
11584
- }
11585
- };
11918
+ /**
11919
+ * The rules are shown in the order they answer a reader's questions: how this value was calculated,
11920
+ * which values went into it, then how each of those was weighted. Grouping them by what they
11921
+ * compute rather than by the page they are documented on is what puts the two calculations either
11922
+ * side of the values they combine.
11923
+ */
11924
+ const GROUPS = [
11925
+ { id: 'value', heading: 'How the value is calculated', pageOrder: pages => pages },
11926
+ // a weight is derived by the product's own rules first, then normalised by the general one
11927
+ { id: 'weights', heading: 'How the weights are calculated', pageOrder: pages => [...pages].reverse() }
11928
+ ];
11586
11929
  // A symbol worth listing under its formula: it carries documentation the reader needs. Constants
11587
11930
  // (e.g. `365`) are self-evident in the formula itself.
11588
11931
  const isDocumented = (binding) => !!binding.symbol && !!binding.description;
@@ -11597,16 +11940,16 @@ const isCompletenessZeroFill = (formula) => bindsTo(formula, 'zero_filled_weight
11597
11940
  // The production-share weight is what combines country aggregations into a World one, so it is only
11598
11941
  // part of how a World aggregation was produced - a country aggregation never applies it.
11599
11942
  const isWorldWeight = (formula) => bindsTo(formula, 'world_production');
11943
+ // The sub-system weight is the product of one sub-aggregation's organic and irrigation factors, so
11944
+ // it is only part of how that sub-aggregation was produced. On the Cycle they are combined into,
11945
+ // each of its symbols holds one value per sub-system rather than the single one bound here.
11946
+ const isSubSystemWeight = (formula) => bindsTo(formula, 'organic_factor');
11600
11947
  // The phase weighting splits a plantation's lifespan between its productive and non-productive
11601
11948
  // years, so it only ran where the product is a permanent crop.
11602
11949
  const isPlantationWeight = (formula) => bindsTo(formula, 'plantation_lifespan');
11603
11950
  // The blank node keys whose terms carry a completeness area.
11604
11951
  const COMPLETENESS_KEYS = ['products', 'emissions', 'inputs', 'practices'];
11605
11952
  const PRODUCTS_KEY = 'products';
11606
- const isMissing = (value) => value === undefined || value === null || value === '';
11607
- // a symbol that should carry a value: the result and the inputs, but never a fixed constant or a
11608
- // quantity the aggregation deliberately does not store
11609
- const isSubstitutable = (binding) => !!binding.key && binding.constant === undefined && !binding.display;
11610
11953
  class NodeAggregatedFormulasComponent {
11611
11954
  constructor() {
11612
11955
  /**
@@ -11636,6 +11979,12 @@ class NodeAggregatedFormulasComponent {
11636
11979
  * is not part of how its values were produced.
11637
11980
  */
11638
11981
  this.worldAggregation = input(false, ...(ngDevMode ? [{ debugName: "worldAggregation" }] : []));
11982
+ /**
11983
+ * Whether the Cycle is one of the sub-aggregations an aggregation is combined from, in which case
11984
+ * the sub-system weighting is one of the stages that produced its values. Defaults to showing the
11985
+ * stage, so a caller that cannot tell keeps the rule documented.
11986
+ */
11987
+ this.subAggregation = input(true, ...(ngDevMode ? [{ debugName: "subAggregation" }] : []));
11639
11988
  // show the substituted formula rather than the symbolic one, when there is anything to substitute
11640
11989
  this.substituted = model(false, ...(ngDevMode ? [{ debugName: "substituted" }] : []));
11641
11990
  /**
@@ -11649,20 +11998,33 @@ class NodeAggregatedFormulasComponent {
11649
11998
  // to the symbolic one, so the toggle is disabled rather than silently doing nothing
11650
11999
  this.hasSubstitutions = computed(() => {
11651
12000
  const values = this.values();
11652
- return this.sections().some(section => section.formulas.some(formula => formula.bindings.some(binding => isSubstitutable(binding) && !isMissing(values[binding.key]))));
12001
+ return this.sections().some(section => section.formulas.some(formula => hasAnySubstitution(values, formula.bindings)));
11653
12002
  }, ...(ngDevMode ? [{ debugName: "hasSubstitutions" }] : []));
11654
12003
  /**
11655
- * Each page with its formulas, rendered symbolically or with values substituted, and the
11656
- * variables documented under each. A variable with nothing to substitute is flagged, so the
11657
- * reader can tell a value that was not recorded from one that is genuinely absent.
12004
+ * The rules that produced this value, grouped by what each one computes.
11658
12005
  */
11659
- this.sections = computed(() => this.pages()
11660
- .map(page => ({
11661
- page,
11662
- ...(PAGE_HEADINGS[page] ?? { heading: 'Formulas', applies: '' }),
11663
- formulas: getFormulas$1(page).filter((formula) => this.applies(formula))
11664
- }))
11665
- .filter(section => section.formulas.length > 0), ...(ngDevMode ? [{ debugName: "sections" }] : []));
12006
+ this.sections = computed(() => GROUPS.map(({ id, heading, pageOrder }) => ({
12007
+ id,
12008
+ heading,
12009
+ formulas: pageOrder(this.pages())
12010
+ .flatMap(page => getFormulas$1(page))
12011
+ .filter(formula => formulaGroup(formula) === id && this.applies(formula))
12012
+ })).filter(section => section.formulas.length > 0), ...(ngDevMode ? [{ debugName: "sections" }] : []));
12013
+ /**
12014
+ * The values this one was combined from, as the aggregation recorded them: one row per Cycle or
12015
+ * sub-aggregation, under the symbols the formulas above read them with. Absent where the
12016
+ * contributors were too many to record, which is every aggregation built straight from source
12017
+ * Cycles - their number is shown instead.
12018
+ */
12019
+ this.contributors = computed(() => {
12020
+ const table = contributorsTable(this.values(), this.sections().flatMap(section => section.formulas));
12021
+ // the node each row links to is built here rather than in the template, so it keeps its
12022
+ // identity between change detection runs
12023
+ return (table && {
12024
+ ...table,
12025
+ rows: table.rows.map(row => ({ ...row, node: { '@type': NodeType.Cycle, '@id': row.id } }))
12026
+ });
12027
+ }, ...(ngDevMode ? [{ debugName: "contributors" }] : []));
11666
12028
  this.renderedSections = computed(() => {
11667
12029
  const values = this.values();
11668
12030
  const showValues = this.substituted() && this.hasSubstitutions();
@@ -11681,7 +12043,10 @@ class NodeAggregatedFormulasComponent {
11681
12043
  description: binding.description,
11682
12044
  // only an input that should have resolved is flagged - a constant, or a quantity the
11683
12045
  // aggregation deliberately does not store, is never substitutable rather than missing
11684
- missing: showValues && isSubstitutable(binding) && isMissing(values[binding.key])
12046
+ missing: showValues && isMissingValue(values, binding),
12047
+ // a symbol reading from a table of contributors too long to record says how many there
12048
+ // were, which is not the same as a value the aggregation never logged
12049
+ note: showValues ? notListedNote(values, binding) : undefined
11685
12050
  }))
11686
12051
  };
11687
12052
  })
@@ -11694,11 +12059,14 @@ class NodeAggregatedFormulasComponent {
11694
12059
  * a World aggregation, and the phase weighting only splits the lifespan of a plantation crop.
11695
12060
  */
11696
12061
  ranForAggregation(formula) {
11697
- if (isWorldWeight(formula))
11698
- return this.worldAggregation();
11699
- if (isPlantationWeight(formula))
11700
- return isPlantation(this.termId() ?? '');
11701
- return true;
12062
+ // the stages that only run for some aggregations, and whether each ran for this one. A formula
12063
+ // matching none of them is part of every aggregation.
12064
+ const stages = [
12065
+ { applies: isWorldWeight, ran: () => this.worldAggregation() },
12066
+ { applies: isSubSystemWeight, ran: () => this.subAggregation() },
12067
+ { applies: isPlantationWeight, ran: () => isPlantation(this.termId() ?? '') }
12068
+ ];
12069
+ return stages.find(({ applies }) => applies(formula))?.ran() ?? true;
11702
12070
  }
11703
12071
  /**
11704
12072
  * Whether the rule applies to the kind of data item shown. With no `nodeKey` every rule is kept,
@@ -11718,12 +12086,133 @@ class NodeAggregatedFormulasComponent {
11718
12086
  return this.ranForAggregation(formula) && this.appliesToNodeKey(formula);
11719
12087
  }
11720
12088
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeAggregatedFormulasComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11721
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeAggregatedFormulasComponent, isStandalone: true, selector: "he-node-aggregated-formulas", inputs: { termType: { classPropertyName: "termType", publicName: "termType", isSignal: true, isRequired: false, transformFunction: null }, termId: { classPropertyName: "termId", publicName: "termId", isSignal: true, isRequired: false, transformFunction: null }, nodeKey: { classPropertyName: "nodeKey", publicName: "nodeKey", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, worldAggregation: { classPropertyName: "worldAggregation", publicName: "worldAggregation", isSignal: true, isRequired: false, transformFunction: null }, substituted: { classPropertyName: "substituted", publicName: "substituted", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { substituted: "substitutedChange" }, ngImport: i0, template: "@if (renderedSections().length) {\n <div class=\"aggregated-formulas\">\n <p class=\"is-size-7 is-mb-2 | formula-intro\">\n Aggregated values are calculated from the underlying Cycles, not measured. These are the rules that produced this\n one, in the order they are applied.\n </p>\n\n <div class=\"aggregated-formulas-rules\">\n @for (section of renderedSections(); track section.page; let first = $first) {\n <he-formula-block\n [formulas]=\"section.formulas\"\n [heading]=\"section.heading\"\n [note]=\"section.applies\"\n [showToggle]=\"first\"\n [hasSubstitutions]=\"hasSubstitutions()\"\n [(substituted)]=\"substituted\"\n emptyTitle=\"No recorded values to substitute\" />\n }\n </div>\n </div>\n}\n", styles: [".formula-intro{opacity:.85}.aggregated-formulas-rules{max-height:50vh;overflow-y:auto;padding-right:.25rem}\n"], dependencies: [{ kind: "component", type: FormulaBlockComponent, selector: "he-formula-block", inputs: ["formulas", "hasSubstitutions", "emptyTitle", "heading", "note", "showToggle", "substituted"], outputs: ["substitutedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
12089
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeAggregatedFormulasComponent, isStandalone: true, selector: "he-node-aggregated-formulas", inputs: { termType: { classPropertyName: "termType", publicName: "termType", isSignal: true, isRequired: false, transformFunction: null }, termId: { classPropertyName: "termId", publicName: "termId", isSignal: true, isRequired: false, transformFunction: null }, nodeKey: { classPropertyName: "nodeKey", publicName: "nodeKey", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, worldAggregation: { classPropertyName: "worldAggregation", publicName: "worldAggregation", isSignal: true, isRequired: false, transformFunction: null }, subAggregation: { classPropertyName: "subAggregation", publicName: "subAggregation", isSignal: true, isRequired: false, transformFunction: null }, substituted: { classPropertyName: "substituted", publicName: "substituted", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { substituted: "substitutedChange" }, ngImport: i0, template: "@if (renderedSections().length) {\n <div class=\"aggregated-formulas\">\n <p class=\"is-size-7 is-mb-2 | formula-intro\">\n Aggregated values are calculated from the underlying Cycles, not measured. These are the rules that produced this\n one, in the order they are applied.\n </p>\n\n <div class=\"is-pt-2 is-pr-1 is-overflow-y-auto | aggregated-formulas-rules\">\n @for (section of renderedSections(); track section.id; let first = $first) {\n <he-formula-block\n [formulas]=\"section.formulas\"\n [heading]=\"section.heading\"\n [collapsible]=\"true\"\n [showToggle]=\"first\"\n [hasSubstitutions]=\"hasSubstitutions()\"\n [(substituted)]=\"substituted\"\n emptyTitle=\"No recorded values to substitute\" />\n\n <!-- the values combined sit between the two calculations: what went in, above how each was weighted -->\n @if (section.id === 'value' && contributors(); as table) {\n <he-collapsible-block heading=\"Values combined\">\n @if (table.rows.length) {\n <table class=\"table is-narrow is-size-7 w-100 | contributors-table\">\n <thead>\n <tr>\n <th>Cycle</th>\n @for (column of table.columns; track column.symbol) {\n <th class=\"has-text-right\">\n <span [heKatex]=\"column.symbol\" [heKatexInline]=\"true\"></span>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of table.rows; track row.id) {\n <tr>\n <td [attr.title]=\"row.id\">\n <he-node-link [node]=\"row.node\" linkClass=\"has-text-white\">\n <span class=\"break-word\">{{ row.label }}</span>\n </he-node-link>\n </td>\n @for (value of row.values; track $index) {\n <td class=\"has-text-right is-nowrap\">{{ value | precision: 4 | default: '-' }}</td>\n }\n </tr>\n }\n </tbody>\n </table>\n } @else {\n <p class=\"is-size-7\">\n Combined from {{ table.count }} Cycles, too many for the aggregation to record one by one.\n </p>\n }\n </he-collapsible-block>\n }\n }\n </div>\n </div>\n}\n", styles: [".formula-intro{opacity:.85}.contributors-table{background-color:transparent;color:inherit}.contributors-table th,.contributors-table td{border-color:#fff3;color:inherit;padding:.15rem .5rem .15rem 0;vertical-align:top}.contributors-table th{opacity:.7}.contributors-table ::ng-deep .katex{font-size:1em}.aggregated-formulas-rules{max-height:50vh}\n"], dependencies: [{ kind: "directive", type: KatexDirective, selector: "[heKatex]", inputs: ["heKatex", "heKatexInline"] }, { kind: "component", type: NodeLinkComponent, selector: "he-node-link", inputs: ["node", "dataState", "showExternalLink", "linkClass"] }, { kind: "component", type: FormulaBlockComponent, selector: "he-formula-block", inputs: ["formulas", "hasSubstitutions", "emptyTitle", "heading", "note", "showToggle", "collapsible", "open", "substituted"], outputs: ["openChange", "substitutedChange"] }, { kind: "component", type: CollapsibleBlockComponent, selector: "he-collapsible-block", inputs: ["heading", "collapsible", "open"], outputs: ["openChange"] }, { kind: "pipe", type: DefaultPipe, name: "default" }, { kind: "pipe", type: PrecisionPipe, name: "precision" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
11722
12090
  }
11723
12091
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeAggregatedFormulasComponent, decorators: [{
11724
12092
  type: Component$1,
11725
- args: [{ selector: 'he-node-aggregated-formulas', changeDetection: ChangeDetectionStrategy.OnPush, imports: [FormulaBlockComponent], template: "@if (renderedSections().length) {\n <div class=\"aggregated-formulas\">\n <p class=\"is-size-7 is-mb-2 | formula-intro\">\n Aggregated values are calculated from the underlying Cycles, not measured. These are the rules that produced this\n one, in the order they are applied.\n </p>\n\n <div class=\"aggregated-formulas-rules\">\n @for (section of renderedSections(); track section.page; let first = $first) {\n <he-formula-block\n [formulas]=\"section.formulas\"\n [heading]=\"section.heading\"\n [note]=\"section.applies\"\n [showToggle]=\"first\"\n [hasSubstitutions]=\"hasSubstitutions()\"\n [(substituted)]=\"substituted\"\n emptyTitle=\"No recorded values to substitute\" />\n }\n </div>\n </div>\n}\n", styles: [".formula-intro{opacity:.85}.aggregated-formulas-rules{max-height:50vh;overflow-y:auto;padding-right:.25rem}\n"] }]
11726
- }], propDecorators: { termType: [{ type: i0.Input, args: [{ isSignal: true, alias: "termType", required: false }] }], termId: [{ type: i0.Input, args: [{ isSignal: true, alias: "termId", required: false }] }], nodeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeKey", required: false }] }], values: [{ type: i0.Input, args: [{ isSignal: true, alias: "values", required: false }] }], worldAggregation: [{ type: i0.Input, args: [{ isSignal: true, alias: "worldAggregation", required: false }] }], substituted: [{ type: i0.Input, args: [{ isSignal: true, alias: "substituted", required: false }] }, { type: i0.Output, args: ["substitutedChange"] }] } });
12093
+ args: [{ selector: 'he-node-aggregated-formulas', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
12094
+ DefaultPipe,
12095
+ PrecisionPipe,
12096
+ KatexDirective,
12097
+ NodeLinkComponent,
12098
+ FormulaBlockComponent,
12099
+ CollapsibleBlockComponent
12100
+ ], template: "@if (renderedSections().length) {\n <div class=\"aggregated-formulas\">\n <p class=\"is-size-7 is-mb-2 | formula-intro\">\n Aggregated values are calculated from the underlying Cycles, not measured. These are the rules that produced this\n one, in the order they are applied.\n </p>\n\n <div class=\"is-pt-2 is-pr-1 is-overflow-y-auto | aggregated-formulas-rules\">\n @for (section of renderedSections(); track section.id; let first = $first) {\n <he-formula-block\n [formulas]=\"section.formulas\"\n [heading]=\"section.heading\"\n [collapsible]=\"true\"\n [showToggle]=\"first\"\n [hasSubstitutions]=\"hasSubstitutions()\"\n [(substituted)]=\"substituted\"\n emptyTitle=\"No recorded values to substitute\" />\n\n <!-- the values combined sit between the two calculations: what went in, above how each was weighted -->\n @if (section.id === 'value' && contributors(); as table) {\n <he-collapsible-block heading=\"Values combined\">\n @if (table.rows.length) {\n <table class=\"table is-narrow is-size-7 w-100 | contributors-table\">\n <thead>\n <tr>\n <th>Cycle</th>\n @for (column of table.columns; track column.symbol) {\n <th class=\"has-text-right\">\n <span [heKatex]=\"column.symbol\" [heKatexInline]=\"true\"></span>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of table.rows; track row.id) {\n <tr>\n <td [attr.title]=\"row.id\">\n <he-node-link [node]=\"row.node\" linkClass=\"has-text-white\">\n <span class=\"break-word\">{{ row.label }}</span>\n </he-node-link>\n </td>\n @for (value of row.values; track $index) {\n <td class=\"has-text-right is-nowrap\">{{ value | precision: 4 | default: '-' }}</td>\n }\n </tr>\n }\n </tbody>\n </table>\n } @else {\n <p class=\"is-size-7\">\n Combined from {{ table.count }} Cycles, too many for the aggregation to record one by one.\n </p>\n }\n </he-collapsible-block>\n }\n }\n </div>\n </div>\n}\n", styles: [".formula-intro{opacity:.85}.contributors-table{background-color:transparent;color:inherit}.contributors-table th,.contributors-table td{border-color:#fff3;color:inherit;padding:.15rem .5rem .15rem 0;vertical-align:top}.contributors-table th{opacity:.7}.contributors-table ::ng-deep .katex{font-size:1em}.aggregated-formulas-rules{max-height:50vh}\n"] }]
12101
+ }], propDecorators: { termType: [{ type: i0.Input, args: [{ isSignal: true, alias: "termType", required: false }] }], termId: [{ type: i0.Input, args: [{ isSignal: true, alias: "termId", required: false }] }], nodeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeKey", required: false }] }], values: [{ type: i0.Input, args: [{ isSignal: true, alias: "values", required: false }] }], worldAggregation: [{ type: i0.Input, args: [{ isSignal: true, alias: "worldAggregation", required: false }] }], subAggregation: [{ type: i0.Input, args: [{ isSignal: true, alias: "subAggregation", required: false }] }], substituted: [{ type: i0.Input, args: [{ isSignal: true, alias: "substituted", required: false }] }, { type: i0.Output, args: ["substitutedChange"] }] } });
12102
+
12103
+ // the `.jlog` entries the aggregation records are tagged with its own model name
12104
+ const JLOG_MODEL = 'aggregation';
12105
+ const LOGS_KEY = 'logs';
12106
+ // the table of contributors an aggregated value is recorded with, one row per Cycle it was
12107
+ // combined from: `id:<cycle id>_value:<value>_weight:<weight>;...`
12108
+ const WEIGHTS_KEY = 'weights';
12109
+ const ID_COLUMN = 'id:';
12110
+ /**
12111
+ * The share of world production a country aggregation was weighted by. It is only recorded on a
12112
+ * Cycle that a World aggregation combines, so its presence is what tells the two node-level
12113
+ * entries apart.
12114
+ */
12115
+ const WORLD_STAGE_KEY = 'world_production';
12116
+ // the sub-system stage: the organic and irrigated factors a sub-aggregation's weight is the product of
12117
+ const SUB_SYSTEM_KEY = 'organic_factor';
12118
+ /**
12119
+ * The lookups an aggregation is weighted by: the shares of the country's area, keyed by country and
12120
+ * period, and the plantation lifespans, keyed by product. Every sub-aggregation of the same country
12121
+ * and period records the same values, so any one of them answers for the aggregation as a whole.
12122
+ */
12123
+ const LOOKUP_KEYS = [
12124
+ 'organic_weight',
12125
+ 'organic_weight_lookup_value',
12126
+ 'irrigated_weight',
12127
+ 'irrigated_area',
12128
+ 'total_area',
12129
+ 'plantation_lifespan',
12130
+ 'plantation_non_productive_lifespan'
12131
+ ];
12132
+ /**
12133
+ * A Cycle combined from sub-aggregations has a handful of contributors (organic or conventional,
12134
+ * irrigated or rainfed); one aggregated straight from source Cycles has hundreds, and those record
12135
+ * no aggregation logs at all - so a longer list means there is nothing to fetch.
12136
+ */
12137
+ const MAX_CONTRIBUTORS = 4;
12138
+ // the quantities of one `.jlog` entry, dropping the `model` marker that tags them
12139
+ const entryValues = (entry) => (entry?.[LOGS_KEY] ?? [])
12140
+ .filter(log => log?.model === JLOG_MODEL)
12141
+ .reduce((values, { model: _model, ...fields }) => ({ ...values, ...fields }), {});
12142
+ /**
12143
+ * The quantities recorded for one blank node. A field of the blank node (e.g. a product's
12144
+ * `economicValueShare`) is logged one level deeper, the way the models' `.jlog` nests it - merge
12145
+ * those in so a formula resolves whichever of the two it describes, the blank node's own values
12146
+ * winning any collision.
12147
+ */
12148
+ const loggedValues = (entry) => {
12149
+ const nested = Object.entries(entry ?? {})
12150
+ .filter(([key]) => key !== LOGS_KEY)
12151
+ .reduce((values, [, field]) => ({ ...values, ...entryValues(field) }), {});
12152
+ return { ...nested, ...entryValues(entry) };
12153
+ };
12154
+ /**
12155
+ * The quantities describing the Cycle itself, recorded once at the top of its `.jlog` rather than
12156
+ * on each data item: how it was weighted into the aggregation it belongs to, and the shares that
12157
+ * weight was derived from.
12158
+ *
12159
+ * A country aggregation records its share of world production here, weighted by `weight` - the same
12160
+ * key the sub-system stage binds to. Drop it there, so the world share can never stand in for a
12161
+ * sub-system weight.
12162
+ */
12163
+ const nodeValues = (jlog) => {
12164
+ const values = entryValues(jlog);
12165
+ return WORLD_STAGE_KEY in values
12166
+ ? Object.fromEntries(Object.entries(values).filter(([key]) => key !== 'weight'))
12167
+ : values;
12168
+ };
12169
+ /**
12170
+ * Whether the sub-system stage is one of the stages that produced this Cycle's values, which its
12171
+ * own node-level entry says. Everywhere else its symbols hold one value per sub-system rather than
12172
+ * the single one the formula binds, so it describes another Cycle rather than this one.
12173
+ *
12174
+ * With nothing logged at all the stage is left to render symbolically, as every other rule does,
12175
+ * rather than hidden on a guess.
12176
+ */
12177
+ const isSubAggregation = (jlog) => {
12178
+ const values = nodeValues(jlog);
12179
+ return isEmpty(values) || SUB_SYSTEM_KEY in values;
12180
+ };
12181
+ // the ids of a packed contributors table, in the order they were recorded
12182
+ const tableIds = (packed) => typeof packed === 'string'
12183
+ ? packed
12184
+ .split(';')
12185
+ .map(row => row.split('_')[0])
12186
+ .filter(column => column.startsWith(ID_COLUMN))
12187
+ .map(column => column.slice(ID_COLUMN.length))
12188
+ : [];
12189
+ /**
12190
+ * The Cycles this one was combined from, named in the contributors table of any of its data items -
12191
+ * the only place a sub-aggregation is named, as the Cycle links the source Cycles it covers rather
12192
+ * than the sub-aggregations it was built from.
12193
+ *
12194
+ * Empty for a sub-aggregation, which records the lookups itself and was built from source Cycles
12195
+ * that log nothing, and when there are more contributors than there are sub-systems - a longer list
12196
+ * is source Cycles rather than sub-aggregations.
12197
+ */
12198
+ const contributorIds = (jlog) => {
12199
+ if (SUB_SYSTEM_KEY in nodeValues(jlog))
12200
+ return [];
12201
+ const tables = Object.values(jlog ?? {})
12202
+ .filter(section => !!section && typeof section === 'object' && !Array.isArray(section))
12203
+ .flatMap(section => Object.values(section).map(entry => entryValues(entry)[WEIGHTS_KEY]));
12204
+ const ids = unique(tableIds(tables.find(table => typeof table === 'string')));
12205
+ return ids.length > MAX_CONTRIBUTORS ? [] : ids;
12206
+ };
12207
+ /**
12208
+ * The country shares, taken from the first contributor that records them. They are the same on
12209
+ * every sub-aggregation of the country and period, so one readable Cycle answers for all of them -
12210
+ * and none being readable leaves those symbols symbolic, as they are today.
12211
+ */
12212
+ const sharedLookups = (jlogs) => (jlogs ?? [])
12213
+ .map(jlog => Object.entries(nodeValues(jlog)).filter(([key]) => LOOKUP_KEYS.includes(key)))
12214
+ .map(entries => Object.fromEntries(entries))
12215
+ .find(values => !isEmpty(values)) ?? {};
11727
12216
 
11728
12217
  // Aggregation has a single "model" - unlike a recalculation, where each term may be produced by a
11729
12218
  // different one - so the column is a constant rather than something resolved per row.
@@ -11748,25 +12237,6 @@ const isWorldAggregation = (node) => {
11748
12237
  ? country['@id'] === WORLD_COUNTRY_ID
11749
12238
  : node?.name?.split(' - ')?.[1]?.trim() === WORLD_COUNTRY_NAME;
11750
12239
  };
11751
- // the `.jlog` entries the aggregation records are tagged with its own model name
11752
- const JLOG_MODEL = 'aggregation';
11753
- const LOGS_KEY = 'logs';
11754
- // the quantities of one `.jlog` entry, dropping the `model` marker that tags them
11755
- const entryValues = (entry) => (entry?.[LOGS_KEY] ?? [])
11756
- .filter(log => log?.model === JLOG_MODEL)
11757
- .reduce((values, { model: _model, ...fields }) => ({ ...values, ...fields }), {});
11758
- /**
11759
- * The quantities recorded for one blank node. A field of the blank node (e.g. a product's
11760
- * `economicValueShare`) is logged one level deeper, the way the models' `.jlog` nests it - merge
11761
- * those in so a formula resolves whichever of the two it describes, the blank node's own values
11762
- * winning any collision.
11763
- */
11764
- const loggedValues = (entry) => {
11765
- const nested = Object.entries(entry ?? {})
11766
- .filter(([key]) => key !== LOGS_KEY)
11767
- .reduce((values, [, field]) => ({ ...values, ...entryValues(field) }), {});
11768
- return { ...nested, ...entryValues(entry) };
11769
- };
11770
12240
  const groupObservations = (rows) => {
11771
12241
  const counts = rows.map(({ observations }) => observations).filter(count => typeof count === 'number');
11772
12242
  if (!counts.length)
@@ -11827,6 +12297,23 @@ class NodeAggregationLogsComponent {
11827
12297
  const index = this.jlogParentIndex();
11828
12298
  return key && typeof index === 'number' && index >= 0 ? (this.jlog()?.[key]?.[index] ?? {}) : this.jlog();
11829
12299
  }, ...(ngDevMode ? [{ debugName: "scopedJlog" }] : []));
12300
+ // the quantities describing the Cycle itself rather than one of its data items, which the
12301
+ // aggregation records once at the top of the `.jlog`
12302
+ this.nodeValues = computed(() => nodeValues(this.scopedJlog()), ...(ngDevMode ? [{ debugName: "nodeValues" }] : []));
12303
+ // whether the sub-system weighting is one of the stages that produced these values
12304
+ this.subAggregation = computed(() => isSubAggregation(this.scopedJlog()), ...(ngDevMode ? [{ debugName: "subAggregation" }] : []));
12305
+ /**
12306
+ * The `.jlog` of every sub-aggregation this Cycle was combined from, for the country shares the
12307
+ * aggregation only records on them. Best effort: a reader without access to a sub-aggregation
12308
+ * gets nothing back, and those symbols stay symbolic - which is how they render today.
12309
+ */
12310
+ this.contributorsResource = rxResource({
12311
+ params: () => ({ ids: contributorIds(this.scopedJlog()) }),
12312
+ stream: ({ params: { ids } }) => ids.length
12313
+ ? forkJoin(ids.map(id => this.nodeLogsModelsService.getJLog$({ '@type': NodeType.Cycle, '@id': id, aggregated: true })))
12314
+ : of([])
12315
+ });
12316
+ this.sharedLookups = computed(() => sharedLookups(this.contributorsResource.value() ?? []), ...(ngDevMode ? [{ debugName: "sharedLookups" }] : []));
11830
12317
  // groups kept open, by term id: expanding is view state, so it survives the rows being rebuilt
11831
12318
  this.openGroups = signal(new Set(), ...(ngDevMode ? [{ debugName: "openGroups" }] : []));
11832
12319
  /**
@@ -11838,10 +12325,13 @@ class NodeAggregationLogsComponent {
11838
12325
  const blankNodes = (this.node()?.[this.nodeKey()] ?? []);
11839
12326
  const logs = this.scopedJlog()?.[this.nodeKey()] ?? {};
11840
12327
  const open = this.openGroups();
12328
+ // a data item's own quantities win over the ones describing the whole Cycle, which in turn win
12329
+ // over the country shares read from a sub-aggregation
12330
+ const shared = { ...this.sharedLookups(), ...this.nodeValues() };
11841
12331
  return groupBlankNodesByTermIdentity(blankNodes, this.nodeType(), this.nodeKey()).map(group => {
11842
12332
  const rows = group.rows.map(row => ({
11843
12333
  ...row,
11844
- values: loggedValues(logs[row.index]),
12334
+ values: { ...shared, ...loggedValues(logs[row.index]) },
11845
12335
  displayValue: propertyValue$1(row.value?.value, group.termId),
11846
12336
  observations: row.value?.observations
11847
12337
  }));
@@ -11863,7 +12353,7 @@ class NodeAggregationLogsComponent {
11863
12353
  return `${row.index}-${row.label}`;
11864
12354
  }
11865
12355
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeAggregationLogsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11866
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeAggregationLogsComponent, isStandalone: true, selector: "he-node-aggregation-logs", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: false, transformFunction: null }, nodeKey: { classPropertyName: "nodeKey", publicName: "nodeKey", isSignal: true, isRequired: false, transformFunction: null }, termType: { classPropertyName: "termType", publicName: "termType", isSignal: true, isRequired: false, transformFunction: null }, termId: { classPropertyName: "termId", publicName: "termId", isSignal: true, isRequired: false, transformFunction: null }, jlogParentKey: { classPropertyName: "jlogParentKey", publicName: "jlogParentKey", isSignal: true, isRequired: false, transformFunction: null }, jlogParentIndex: { classPropertyName: "jlogParentIndex", publicName: "jlogParentIndex", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"is-flex is-gap-8 is-justify-content-space-between is-align-items-center is-my-2\">\n <div>\n <ng-content />\n </div>\n</div>\n\n<he-data-table class=\"is-mt-2 is-mb-1 is-bordered\" [small]=\"true\" maxHeight=\"320\">\n <table class=\"table is-fullwidth is-narrow is-striped\">\n <thead>\n <tr>\n <th class=\"width-auto has-border-right\"></th>\n <th class=\"has-border-right\"><span>Units</span></th>\n <th class=\"has-border-right\"><span>Aggregated</span></th>\n <th class=\"has-border-right\">\n <span title=\"The number of Cycles the value was aggregated from\">Observations</span>\n </th>\n <th class=\"has-border-right\"><span>Model</span></th>\n </tr>\n </thead>\n <tbody>\n @if (groups().length === 0) {\n <tr>\n <td class=\"has-border-right has-text-centered\" colspan=\"100\">\n <p class=\"is-p-1\">No aggregated data to show.</p>\n </td>\n </tr>\n }\n @for (group of groups(); track trackByGroup($index, group)) {\n @let single = group.rows.length === 1;\n <tr [class.has-sub-rows]=\"group.canOpen\" [class.is-open]=\"group.isOpen\">\n <td class=\"width-auto has-border-right is-nowrap\" [attr.title]=\"group.term?.name\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4\">\n @if (group.canOpen) {\n <a class=\"open-node\" (click)=\"toggleGroup(group)\">\n <he-svg-icon [name]=\"group.isOpen ? 'chevron-down' : 'chevron-right'\" />\n </a>\n }\n <he-node-link class=\"is-inline-block is-pre-wrap is-pr-2\" [node]=\"group.term\">\n <span class=\"break-word\" [innerHtml]=\"group.term?.name | compound: group.term?.termType\"></span>\n </he-node-link>\n </div>\n </td>\n\n @if (single) {\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row: group.rows[0], term: group.term }\" />\n <ng-container *ngTemplateOutlet=\"modelCell; context: { row: group.rows[0] }\" />\n } @else {\n <td class=\"has-border-right\">\n <span class=\"is-nowrap\" [innerHtml]=\"group.term?.units | compound\"></span>\n </td>\n <td class=\"has-border-right is-nowrap\">\n @if (group.valueFormula) {\n <span\n class=\"has-formula\"\n [ngbPopover]=\"group.valueFormula\"\n popoverClass=\"is-narrow\"\n triggers=\"click\"\n container=\"body\">\n {{ group.value | precision: 3 | default: '-' }}\n </span>\n } @else {\n {{ group.value | precision: 3 | default: '-' }}\n }\n </td>\n <td class=\"has-border-right is-nowrap\">{{ group.observations | default: '-' }}</td>\n <ng-container *ngTemplateOutlet=\"modelCell\" />\n }\n </tr>\n\n @if (!single && group.isOpen) {\n @for (row of group.rows; track trackByRow($index, row)) {\n <tr class=\"is-sub-row\">\n <td class=\"width-auto has-border-right\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4 is-pl-3\">\n <he-svg-icon class=\"sub-sub-row-icon\" name=\"chevron-double-right\" />\n <he-blank-node-identity\n [segments]=\"row.segments\"\n [scalars]=\"row.scalars\"\n [type]=\"group.type\"\n [fallback]=\"'entry ' + (row.index + 1)\" />\n </div>\n </td>\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row, term: group.term }\" />\n <ng-container *ngTemplateOutlet=\"modelCell; context: { row }\" />\n </tr>\n }\n }\n }\n </tbody>\n </table>\n</he-data-table>\n\n<ng-template #valueCells let-row=\"row\" let-term=\"term\">\n <td class=\"has-border-right\">\n <span class=\"is-nowrap\" [innerHtml]=\"term?.units | compound\"></span>\n </td>\n <td class=\"has-border-right is-nowrap\">\n {{ row.displayValue | precision: 3 | default: '-' }}\n </td>\n <td class=\"has-border-right is-nowrap\">\n {{ row.observations | default: '-' }}\n </td>\n</ng-template>\n\n<!-- `row` is left out on a term group: the rules are the same for every entry it holds, so the guide\n is shown there too, but the values to substitute belong to one entry - those stay on the sub-rows -->\n<ng-template #modelCell let-row=\"row\">\n <td class=\"has-border-right\">\n <div class=\"is-flex is-align-self-stretch is-align-items-center is-gap-8\">\n <span class=\"is-flex-grow-1 is-nowrap\">{{ modelName }}</span>\n\n <div class=\"is-flex is-gap-4 is-flex-shrink-0 is-align-items-center\">\n @if (row) {\n <span\n class=\"is-nowrap is-clickable\"\n [ngbPopover]=\"aggregationRules\"\n autoClose=\"outside\"\n popoverClass=\"is-narrow\"\n triggers=\"manual\"\n #p=\"ngbPopover\"\n placement=\"bottom left right auto\"\n container=\"body\"\n (click)=\"p.isOpen() ? p.close() : p.open({ values: row.values })\">\n <span class=\"has-text-link\">Logs</span>\n </span>\n } @else {\n <span class=\"has-text-grey is-nowrap\">Expand for logs</span>\n }\n\n <div class=\"vertical-divider\"></div>\n\n @if (guideEnabled) {\n <he-guide-overlay [pageId]=\"guidePage()\" [width]=\"500\" />\n } @else {\n <a [href]=\"guideHref()\" target=\"_blank\" rel=\"noopener\" (click)=\"$event.stopPropagation()\">\n <span>Docs</span>\n <he-svg-icon name=\"external-link\" class=\"ml-2\" />\n </a>\n }\n </div>\n </div>\n </td>\n</ng-template>\n\n<ng-template #aggregationRules let-values=\"values\">\n <he-node-aggregated-formulas\n [termType]=\"termType()\"\n [termId]=\"termId()\"\n [nodeKey]=\"nodeKey()\"\n [values]=\"values\"\n [worldAggregation]=\"worldAggregation()\" />\n</ng-template>\n", styles: [":host{display:block}:host .vertical-divider{width:1px;height:20px;background:#dbe3ea}:host .has-formula{cursor:help;border-bottom:1px dotted currentColor}::ng-deep .table{background-color:transparent}::ng-deep .table td.has-border-right{box-shadow:1px 0 #4c7194}::ng-deep .table td>div{min-height:24px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before,::ng-deep .table .is-sub-row>td:first-child:before{display:block;position:absolute;content:\" \";background-color:#4c719433;height:100%;width:1px;top:0;left:14px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before{top:25px}::ng-deep .table .is-sub-row .open-node>he-svg-icon,::ng-deep .table .is-sub-row .sub-sub-row-icon{height:16px!important;width:16px!important}::ng-deep .table .is-sub-sub-row td:first-child{padding-left:24px}::ng-deep .table th:last-child,::ng-deep .table td:last-child{min-width:12rem}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgbPopover, selector: "[ngbPopover]", inputs: ["animation", "autoClose", "ngbPopover", "popoverTitle", "placement", "popperOptions", "triggers", "positionTarget", "container", "disablePopover", "popoverClass", "popoverContext", "openDelay", "closeDelay"], outputs: ["shown", "hidden"], exportAs: ["ngbPopover"] }, { kind: "component", type: DataTableComponent, selector: "he-data-table", inputs: ["minHeight", "maxHeight", "small"] }, { kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "component", type: NodeLinkComponent, selector: "he-node-link", inputs: ["node", "dataState", "showExternalLink", "linkClass"] }, { kind: "component", type: BlankNodeIdentityComponent, selector: "he-blank-node-identity", inputs: ["segments", "scalars", "type", "fallback"] }, { kind: "component", type: GuideOverlayComponent, selector: "he-guide-overlay", inputs: ["pageId", "width", "height", "positions"], outputs: ["widthChange", "heightChange"] }, { kind: "component", type: NodeAggregatedFormulasComponent, selector: "he-node-aggregated-formulas", inputs: ["termType", "termId", "nodeKey", "values", "worldAggregation", "substituted"], outputs: ["substitutedChange"] }, { kind: "pipe", type: CompoundPipe, name: "compound" }, { kind: "pipe", type: DefaultPipe, name: "default" }, { kind: "pipe", type: PrecisionPipe, name: "precision" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
12356
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeAggregationLogsComponent, isStandalone: true, selector: "he-node-aggregation-logs", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: false, transformFunction: null }, nodeKey: { classPropertyName: "nodeKey", publicName: "nodeKey", isSignal: true, isRequired: false, transformFunction: null }, termType: { classPropertyName: "termType", publicName: "termType", isSignal: true, isRequired: false, transformFunction: null }, termId: { classPropertyName: "termId", publicName: "termId", isSignal: true, isRequired: false, transformFunction: null }, jlogParentKey: { classPropertyName: "jlogParentKey", publicName: "jlogParentKey", isSignal: true, isRequired: false, transformFunction: null }, jlogParentIndex: { classPropertyName: "jlogParentIndex", publicName: "jlogParentIndex", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"is-flex is-gap-8 is-justify-content-space-between is-align-items-center is-my-2\">\n <div>\n <ng-content />\n </div>\n</div>\n\n<he-data-table class=\"is-mt-2 is-mb-1 is-bordered\" [small]=\"true\" maxHeight=\"320\">\n <table class=\"table is-fullwidth is-narrow is-striped\">\n <thead>\n <tr>\n <th class=\"width-auto has-border-right\"></th>\n <th class=\"has-border-right\"><span>Units</span></th>\n <th class=\"has-border-right\"><span>Aggregated</span></th>\n <th class=\"has-border-right\">\n <span title=\"The number of Cycles the value was aggregated from\">Observations</span>\n </th>\n <th class=\"has-border-right\"><span>Model</span></th>\n </tr>\n </thead>\n <tbody>\n @if (groups().length === 0) {\n <tr>\n <td class=\"has-border-right has-text-centered\" colspan=\"100\">\n <p class=\"is-p-1\">No aggregated data to show.</p>\n </td>\n </tr>\n }\n @for (group of groups(); track trackByGroup($index, group)) {\n @let single = group.rows.length === 1;\n <tr [class.has-sub-rows]=\"group.canOpen\" [class.is-open]=\"group.isOpen\">\n <td class=\"width-auto has-border-right is-nowrap\" [attr.title]=\"group.term?.name\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4\">\n @if (group.canOpen) {\n <a class=\"open-node\" (click)=\"toggleGroup(group)\">\n <he-svg-icon [name]=\"group.isOpen ? 'chevron-down' : 'chevron-right'\" />\n </a>\n }\n <he-node-link class=\"is-inline-block is-pre-wrap is-pr-2\" [node]=\"group.term\">\n <span class=\"break-word\" [innerHtml]=\"group.term?.name | compound: group.term?.termType\"></span>\n </he-node-link>\n </div>\n </td>\n\n @if (single) {\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row: group.rows[0], term: group.term }\" />\n <ng-container *ngTemplateOutlet=\"modelCell; context: { row: group.rows[0] }\" />\n } @else {\n <td class=\"has-border-right\">\n <span class=\"is-nowrap\" [innerHtml]=\"group.term?.units | compound\"></span>\n </td>\n <td class=\"has-border-right is-nowrap\">\n @if (group.valueFormula) {\n <span\n class=\"has-formula\"\n [ngbPopover]=\"group.valueFormula\"\n popoverClass=\"is-narrow\"\n triggers=\"click\"\n container=\"body\">\n {{ group.value | precision: 3 | default: '-' }}\n </span>\n } @else {\n {{ group.value | precision: 3 | default: '-' }}\n }\n </td>\n <td class=\"has-border-right is-nowrap\">{{ group.observations | default: '-' }}</td>\n <ng-container *ngTemplateOutlet=\"modelCell\" />\n }\n </tr>\n\n @if (!single && group.isOpen) {\n @for (row of group.rows; track trackByRow($index, row)) {\n <tr class=\"is-sub-row\">\n <td class=\"width-auto has-border-right\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4 is-pl-3\">\n <he-svg-icon class=\"sub-sub-row-icon\" name=\"chevron-double-right\" />\n <he-blank-node-identity\n [segments]=\"row.segments\"\n [scalars]=\"row.scalars\"\n [type]=\"group.type\"\n [fallback]=\"'entry ' + (row.index + 1)\" />\n </div>\n </td>\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row, term: group.term }\" />\n <ng-container *ngTemplateOutlet=\"modelCell; context: { row }\" />\n </tr>\n }\n }\n }\n </tbody>\n </table>\n</he-data-table>\n\n<ng-template #valueCells let-row=\"row\" let-term=\"term\">\n <td class=\"has-border-right\">\n <span class=\"is-nowrap\" [innerHtml]=\"term?.units | compound\"></span>\n </td>\n <td class=\"has-border-right is-nowrap\">\n {{ row.displayValue | precision: 3 | default: '-' }}\n </td>\n <td class=\"has-border-right is-nowrap\">\n {{ row.observations | default: '-' }}\n </td>\n</ng-template>\n\n<!-- `row` is left out on a term group: the rules are the same for every entry it holds, so the guide\n is shown there too, but the values to substitute belong to one entry - those stay on the sub-rows -->\n<ng-template #modelCell let-row=\"row\">\n <td class=\"has-border-right\">\n <div class=\"is-flex is-align-self-stretch is-align-items-center is-gap-8\">\n <span class=\"is-flex-grow-1 is-nowrap\">{{ modelName }}</span>\n\n <div class=\"is-flex is-gap-4 is-flex-shrink-0 is-align-items-center\">\n @if (row) {\n <span\n class=\"is-nowrap is-clickable\"\n [ngbPopover]=\"aggregationRules\"\n autoClose=\"outside\"\n popoverClass=\"is-narrow\"\n triggers=\"manual\"\n #p=\"ngbPopover\"\n placement=\"bottom left right auto\"\n container=\"body\"\n (click)=\"p.isOpen() ? p.close() : p.open({ values: row.values })\">\n <span class=\"has-text-link\">Logs</span>\n </span>\n } @else {\n <span class=\"has-text-grey is-nowrap\">Expand for logs</span>\n }\n\n <div class=\"vertical-divider\"></div>\n\n @if (guideEnabled) {\n <he-guide-overlay [pageId]=\"guidePage()\" [width]=\"500\" />\n } @else {\n <a [href]=\"guideHref()\" target=\"_blank\" rel=\"noopener\" (click)=\"$event.stopPropagation()\">\n <span>Docs</span>\n <he-svg-icon name=\"external-link\" class=\"ml-2\" />\n </a>\n }\n </div>\n </div>\n </td>\n</ng-template>\n\n<ng-template #aggregationRules let-values=\"values\">\n <he-node-aggregated-formulas\n [termType]=\"termType()\"\n [termId]=\"termId()\"\n [nodeKey]=\"nodeKey()\"\n [values]=\"values\"\n [worldAggregation]=\"worldAggregation()\"\n [subAggregation]=\"subAggregation()\" />\n</ng-template>\n", styles: [":host{display:block}:host .vertical-divider{width:1px;height:20px;background:#dbe3ea}:host .has-formula{cursor:help;border-bottom:1px dotted currentColor}::ng-deep .table{background-color:transparent}::ng-deep .table td.has-border-right{box-shadow:1px 0 #4c7194}::ng-deep .table td>div{min-height:24px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before,::ng-deep .table .is-sub-row>td:first-child:before{display:block;position:absolute;content:\" \";background-color:#4c719433;height:100%;width:1px;top:0;left:14px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before{top:25px}::ng-deep .table .is-sub-row .open-node>he-svg-icon,::ng-deep .table .is-sub-row .sub-sub-row-icon{height:16px!important;width:16px!important}::ng-deep .table .is-sub-sub-row td:first-child{padding-left:24px}::ng-deep .table th:last-child,::ng-deep .table td:last-child{min-width:12rem}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgbPopover, selector: "[ngbPopover]", inputs: ["animation", "autoClose", "ngbPopover", "popoverTitle", "placement", "popperOptions", "triggers", "positionTarget", "container", "disablePopover", "popoverClass", "popoverContext", "openDelay", "closeDelay"], outputs: ["shown", "hidden"], exportAs: ["ngbPopover"] }, { kind: "component", type: DataTableComponent, selector: "he-data-table", inputs: ["minHeight", "maxHeight", "small"] }, { kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "component", type: NodeLinkComponent, selector: "he-node-link", inputs: ["node", "dataState", "showExternalLink", "linkClass"] }, { kind: "component", type: BlankNodeIdentityComponent, selector: "he-blank-node-identity", inputs: ["segments", "scalars", "type", "fallback"] }, { kind: "component", type: GuideOverlayComponent, selector: "he-guide-overlay", inputs: ["pageId", "width", "height", "positions"], outputs: ["widthChange", "heightChange"] }, { kind: "component", type: NodeAggregatedFormulasComponent, selector: "he-node-aggregated-formulas", inputs: ["termType", "termId", "nodeKey", "values", "worldAggregation", "subAggregation", "substituted"], outputs: ["substitutedChange"] }, { kind: "pipe", type: CompoundPipe, name: "compound" }, { kind: "pipe", type: DefaultPipe, name: "default" }, { kind: "pipe", type: PrecisionPipe, name: "precision" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
11867
12357
  }
11868
12358
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeAggregationLogsComponent, decorators: [{
11869
12359
  type: Component$1,
@@ -11879,7 +12369,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
11879
12369
  BlankNodeIdentityComponent,
11880
12370
  GuideOverlayComponent,
11881
12371
  NodeAggregatedFormulasComponent
11882
- ], template: "<div class=\"is-flex is-gap-8 is-justify-content-space-between is-align-items-center is-my-2\">\n <div>\n <ng-content />\n </div>\n</div>\n\n<he-data-table class=\"is-mt-2 is-mb-1 is-bordered\" [small]=\"true\" maxHeight=\"320\">\n <table class=\"table is-fullwidth is-narrow is-striped\">\n <thead>\n <tr>\n <th class=\"width-auto has-border-right\"></th>\n <th class=\"has-border-right\"><span>Units</span></th>\n <th class=\"has-border-right\"><span>Aggregated</span></th>\n <th class=\"has-border-right\">\n <span title=\"The number of Cycles the value was aggregated from\">Observations</span>\n </th>\n <th class=\"has-border-right\"><span>Model</span></th>\n </tr>\n </thead>\n <tbody>\n @if (groups().length === 0) {\n <tr>\n <td class=\"has-border-right has-text-centered\" colspan=\"100\">\n <p class=\"is-p-1\">No aggregated data to show.</p>\n </td>\n </tr>\n }\n @for (group of groups(); track trackByGroup($index, group)) {\n @let single = group.rows.length === 1;\n <tr [class.has-sub-rows]=\"group.canOpen\" [class.is-open]=\"group.isOpen\">\n <td class=\"width-auto has-border-right is-nowrap\" [attr.title]=\"group.term?.name\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4\">\n @if (group.canOpen) {\n <a class=\"open-node\" (click)=\"toggleGroup(group)\">\n <he-svg-icon [name]=\"group.isOpen ? 'chevron-down' : 'chevron-right'\" />\n </a>\n }\n <he-node-link class=\"is-inline-block is-pre-wrap is-pr-2\" [node]=\"group.term\">\n <span class=\"break-word\" [innerHtml]=\"group.term?.name | compound: group.term?.termType\"></span>\n </he-node-link>\n </div>\n </td>\n\n @if (single) {\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row: group.rows[0], term: group.term }\" />\n <ng-container *ngTemplateOutlet=\"modelCell; context: { row: group.rows[0] }\" />\n } @else {\n <td class=\"has-border-right\">\n <span class=\"is-nowrap\" [innerHtml]=\"group.term?.units | compound\"></span>\n </td>\n <td class=\"has-border-right is-nowrap\">\n @if (group.valueFormula) {\n <span\n class=\"has-formula\"\n [ngbPopover]=\"group.valueFormula\"\n popoverClass=\"is-narrow\"\n triggers=\"click\"\n container=\"body\">\n {{ group.value | precision: 3 | default: '-' }}\n </span>\n } @else {\n {{ group.value | precision: 3 | default: '-' }}\n }\n </td>\n <td class=\"has-border-right is-nowrap\">{{ group.observations | default: '-' }}</td>\n <ng-container *ngTemplateOutlet=\"modelCell\" />\n }\n </tr>\n\n @if (!single && group.isOpen) {\n @for (row of group.rows; track trackByRow($index, row)) {\n <tr class=\"is-sub-row\">\n <td class=\"width-auto has-border-right\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4 is-pl-3\">\n <he-svg-icon class=\"sub-sub-row-icon\" name=\"chevron-double-right\" />\n <he-blank-node-identity\n [segments]=\"row.segments\"\n [scalars]=\"row.scalars\"\n [type]=\"group.type\"\n [fallback]=\"'entry ' + (row.index + 1)\" />\n </div>\n </td>\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row, term: group.term }\" />\n <ng-container *ngTemplateOutlet=\"modelCell; context: { row }\" />\n </tr>\n }\n }\n }\n </tbody>\n </table>\n</he-data-table>\n\n<ng-template #valueCells let-row=\"row\" let-term=\"term\">\n <td class=\"has-border-right\">\n <span class=\"is-nowrap\" [innerHtml]=\"term?.units | compound\"></span>\n </td>\n <td class=\"has-border-right is-nowrap\">\n {{ row.displayValue | precision: 3 | default: '-' }}\n </td>\n <td class=\"has-border-right is-nowrap\">\n {{ row.observations | default: '-' }}\n </td>\n</ng-template>\n\n<!-- `row` is left out on a term group: the rules are the same for every entry it holds, so the guide\n is shown there too, but the values to substitute belong to one entry - those stay on the sub-rows -->\n<ng-template #modelCell let-row=\"row\">\n <td class=\"has-border-right\">\n <div class=\"is-flex is-align-self-stretch is-align-items-center is-gap-8\">\n <span class=\"is-flex-grow-1 is-nowrap\">{{ modelName }}</span>\n\n <div class=\"is-flex is-gap-4 is-flex-shrink-0 is-align-items-center\">\n @if (row) {\n <span\n class=\"is-nowrap is-clickable\"\n [ngbPopover]=\"aggregationRules\"\n autoClose=\"outside\"\n popoverClass=\"is-narrow\"\n triggers=\"manual\"\n #p=\"ngbPopover\"\n placement=\"bottom left right auto\"\n container=\"body\"\n (click)=\"p.isOpen() ? p.close() : p.open({ values: row.values })\">\n <span class=\"has-text-link\">Logs</span>\n </span>\n } @else {\n <span class=\"has-text-grey is-nowrap\">Expand for logs</span>\n }\n\n <div class=\"vertical-divider\"></div>\n\n @if (guideEnabled) {\n <he-guide-overlay [pageId]=\"guidePage()\" [width]=\"500\" />\n } @else {\n <a [href]=\"guideHref()\" target=\"_blank\" rel=\"noopener\" (click)=\"$event.stopPropagation()\">\n <span>Docs</span>\n <he-svg-icon name=\"external-link\" class=\"ml-2\" />\n </a>\n }\n </div>\n </div>\n </td>\n</ng-template>\n\n<ng-template #aggregationRules let-values=\"values\">\n <he-node-aggregated-formulas\n [termType]=\"termType()\"\n [termId]=\"termId()\"\n [nodeKey]=\"nodeKey()\"\n [values]=\"values\"\n [worldAggregation]=\"worldAggregation()\" />\n</ng-template>\n", styles: [":host{display:block}:host .vertical-divider{width:1px;height:20px;background:#dbe3ea}:host .has-formula{cursor:help;border-bottom:1px dotted currentColor}::ng-deep .table{background-color:transparent}::ng-deep .table td.has-border-right{box-shadow:1px 0 #4c7194}::ng-deep .table td>div{min-height:24px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before,::ng-deep .table .is-sub-row>td:first-child:before{display:block;position:absolute;content:\" \";background-color:#4c719433;height:100%;width:1px;top:0;left:14px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before{top:25px}::ng-deep .table .is-sub-row .open-node>he-svg-icon,::ng-deep .table .is-sub-row .sub-sub-row-icon{height:16px!important;width:16px!important}::ng-deep .table .is-sub-sub-row td:first-child{padding-left:24px}::ng-deep .table th:last-child,::ng-deep .table td:last-child{min-width:12rem}\n"] }]
12372
+ ], template: "<div class=\"is-flex is-gap-8 is-justify-content-space-between is-align-items-center is-my-2\">\n <div>\n <ng-content />\n </div>\n</div>\n\n<he-data-table class=\"is-mt-2 is-mb-1 is-bordered\" [small]=\"true\" maxHeight=\"320\">\n <table class=\"table is-fullwidth is-narrow is-striped\">\n <thead>\n <tr>\n <th class=\"width-auto has-border-right\"></th>\n <th class=\"has-border-right\"><span>Units</span></th>\n <th class=\"has-border-right\"><span>Aggregated</span></th>\n <th class=\"has-border-right\">\n <span title=\"The number of Cycles the value was aggregated from\">Observations</span>\n </th>\n <th class=\"has-border-right\"><span>Model</span></th>\n </tr>\n </thead>\n <tbody>\n @if (groups().length === 0) {\n <tr>\n <td class=\"has-border-right has-text-centered\" colspan=\"100\">\n <p class=\"is-p-1\">No aggregated data to show.</p>\n </td>\n </tr>\n }\n @for (group of groups(); track trackByGroup($index, group)) {\n @let single = group.rows.length === 1;\n <tr [class.has-sub-rows]=\"group.canOpen\" [class.is-open]=\"group.isOpen\">\n <td class=\"width-auto has-border-right is-nowrap\" [attr.title]=\"group.term?.name\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4\">\n @if (group.canOpen) {\n <a class=\"open-node\" (click)=\"toggleGroup(group)\">\n <he-svg-icon [name]=\"group.isOpen ? 'chevron-down' : 'chevron-right'\" />\n </a>\n }\n <he-node-link class=\"is-inline-block is-pre-wrap is-pr-2\" [node]=\"group.term\">\n <span class=\"break-word\" [innerHtml]=\"group.term?.name | compound: group.term?.termType\"></span>\n </he-node-link>\n </div>\n </td>\n\n @if (single) {\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row: group.rows[0], term: group.term }\" />\n <ng-container *ngTemplateOutlet=\"modelCell; context: { row: group.rows[0] }\" />\n } @else {\n <td class=\"has-border-right\">\n <span class=\"is-nowrap\" [innerHtml]=\"group.term?.units | compound\"></span>\n </td>\n <td class=\"has-border-right is-nowrap\">\n @if (group.valueFormula) {\n <span\n class=\"has-formula\"\n [ngbPopover]=\"group.valueFormula\"\n popoverClass=\"is-narrow\"\n triggers=\"click\"\n container=\"body\">\n {{ group.value | precision: 3 | default: '-' }}\n </span>\n } @else {\n {{ group.value | precision: 3 | default: '-' }}\n }\n </td>\n <td class=\"has-border-right is-nowrap\">{{ group.observations | default: '-' }}</td>\n <ng-container *ngTemplateOutlet=\"modelCell\" />\n }\n </tr>\n\n @if (!single && group.isOpen) {\n @for (row of group.rows; track trackByRow($index, row)) {\n <tr class=\"is-sub-row\">\n <td class=\"width-auto has-border-right\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4 is-pl-3\">\n <he-svg-icon class=\"sub-sub-row-icon\" name=\"chevron-double-right\" />\n <he-blank-node-identity\n [segments]=\"row.segments\"\n [scalars]=\"row.scalars\"\n [type]=\"group.type\"\n [fallback]=\"'entry ' + (row.index + 1)\" />\n </div>\n </td>\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row, term: group.term }\" />\n <ng-container *ngTemplateOutlet=\"modelCell; context: { row }\" />\n </tr>\n }\n }\n }\n </tbody>\n </table>\n</he-data-table>\n\n<ng-template #valueCells let-row=\"row\" let-term=\"term\">\n <td class=\"has-border-right\">\n <span class=\"is-nowrap\" [innerHtml]=\"term?.units | compound\"></span>\n </td>\n <td class=\"has-border-right is-nowrap\">\n {{ row.displayValue | precision: 3 | default: '-' }}\n </td>\n <td class=\"has-border-right is-nowrap\">\n {{ row.observations | default: '-' }}\n </td>\n</ng-template>\n\n<!-- `row` is left out on a term group: the rules are the same for every entry it holds, so the guide\n is shown there too, but the values to substitute belong to one entry - those stay on the sub-rows -->\n<ng-template #modelCell let-row=\"row\">\n <td class=\"has-border-right\">\n <div class=\"is-flex is-align-self-stretch is-align-items-center is-gap-8\">\n <span class=\"is-flex-grow-1 is-nowrap\">{{ modelName }}</span>\n\n <div class=\"is-flex is-gap-4 is-flex-shrink-0 is-align-items-center\">\n @if (row) {\n <span\n class=\"is-nowrap is-clickable\"\n [ngbPopover]=\"aggregationRules\"\n autoClose=\"outside\"\n popoverClass=\"is-narrow\"\n triggers=\"manual\"\n #p=\"ngbPopover\"\n placement=\"bottom left right auto\"\n container=\"body\"\n (click)=\"p.isOpen() ? p.close() : p.open({ values: row.values })\">\n <span class=\"has-text-link\">Logs</span>\n </span>\n } @else {\n <span class=\"has-text-grey is-nowrap\">Expand for logs</span>\n }\n\n <div class=\"vertical-divider\"></div>\n\n @if (guideEnabled) {\n <he-guide-overlay [pageId]=\"guidePage()\" [width]=\"500\" />\n } @else {\n <a [href]=\"guideHref()\" target=\"_blank\" rel=\"noopener\" (click)=\"$event.stopPropagation()\">\n <span>Docs</span>\n <he-svg-icon name=\"external-link\" class=\"ml-2\" />\n </a>\n }\n </div>\n </div>\n </td>\n</ng-template>\n\n<ng-template #aggregationRules let-values=\"values\">\n <he-node-aggregated-formulas\n [termType]=\"termType()\"\n [termId]=\"termId()\"\n [nodeKey]=\"nodeKey()\"\n [values]=\"values\"\n [worldAggregation]=\"worldAggregation()\"\n [subAggregation]=\"subAggregation()\" />\n</ng-template>\n", styles: [":host{display:block}:host .vertical-divider{width:1px;height:20px;background:#dbe3ea}:host .has-formula{cursor:help;border-bottom:1px dotted currentColor}::ng-deep .table{background-color:transparent}::ng-deep .table td.has-border-right{box-shadow:1px 0 #4c7194}::ng-deep .table td>div{min-height:24px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before,::ng-deep .table .is-sub-row>td:first-child:before{display:block;position:absolute;content:\" \";background-color:#4c719433;height:100%;width:1px;top:0;left:14px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before{top:25px}::ng-deep .table .is-sub-row .open-node>he-svg-icon,::ng-deep .table .is-sub-row .sub-sub-row-icon{height:16px!important;width:16px!important}::ng-deep .table .is-sub-sub-row td:first-child{padding-left:24px}::ng-deep .table th:last-child,::ng-deep .table td:last-child{min-width:12rem}\n"] }]
11883
12373
  }], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: false }] }], nodeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeKey", required: false }] }], termType: [{ type: i0.Input, args: [{ isSignal: true, alias: "termType", required: false }] }], termId: [{ type: i0.Input, args: [{ isSignal: true, alias: "termId", required: false }] }], jlogParentKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "jlogParentKey", required: false }] }], jlogParentIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "jlogParentIndex", required: false }] }] } });
11884
12374
 
11885
12375
  const isValidDate = (date) => (date || '').trim().length === 10;
@@ -12479,18 +12969,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
12479
12969
 
12480
12970
  // default for all product termType, can be overwritten
12481
12971
  const defaultMinNbObservations = 50;
12972
+ // the `.jlog` section the aggregation stores the quality score in, and the model tagging its entries
12973
+ const jlogKey = 'aggregatedQualityScore';
12974
+ const jlogModel = 'aggregation';
12975
+ const isTrue = (value) => value === true || value === 'True';
12976
+ /**
12977
+ * Whether a quantity was logged: one that was not found is logged as `None`, which the `.jlog`
12978
+ * carries as it is written.
12979
+ */
12980
+ const hasLogValue = (value) => !isUndefined(value) && !isNaN(+value);
12482
12981
  const isScoreValid = {
12483
- 0: ({ all_included }) => all_included === 'True',
12982
+ 0: ({ all_included }) => isTrue(all_included),
12484
12983
  1: ({ nb_observations, min_nb_observations }) => +nb_observations >= (+min_nb_observations || defaultMinNbObservations),
12485
12984
  2: ({ delta, delta_min, yield_delta, yield_delta_min }) => isUndefined(delta) ? +yield_delta <= +yield_delta_min : +delta <= +delta_min,
12486
- 3: ({ is_complete }) => is_complete === 'True',
12985
+ 3: ({ is_complete }) => isTrue(is_complete),
12487
12986
  4: ({ production_delta, production_delta_min }) => +production_delta >= +production_delta_min
12488
12987
  };
12988
+ /**
12989
+ * The quality score recorded in the `.jlog`, merged into a single set of values.
12990
+ * Empty for every aggregation that predates it, which only has the text logs.
12991
+ */
12992
+ const jlogScore = (jlog) => (jlog?.[jlogKey]?.logs ?? [])
12993
+ .filter(({ model }) => model === jlogModel)
12994
+ .reduce((values, { model: _model, ...fields }) => ({ ...values, ...fields }), {});
12995
+ const toValidScores = (logs) => Object.fromEntries(Object.entries(isScoreValid).map(([key, isValid]) => [key, isValid(logs)]));
12996
+
12489
12997
  const getCountryName = (node) => node?.site?.country?.name || node.name?.split(' - ')?.[1]?.trim();
12490
12998
  class NodeAggregatedQualityScoreComponent {
12491
12999
  constructor() {
12492
13000
  this.searchService = inject(HeSearchService);
12493
13001
  this.nodeService = inject(HeNodeService);
13002
+ this.nodeLogsModelsService = inject(NodeLogsModelsService);
12494
13003
  this.node = input.required(...(ngDevMode ? [{ debugName: "node" }] : []));
12495
13004
  this.country = input(...(ngDevMode ? [undefined, { debugName: "country" }] : []));
12496
13005
  /**
@@ -12530,22 +13039,34 @@ class NodeAggregatedQualityScoreComponent {
12530
13039
  : of(undefined)
12531
13040
  });
12532
13041
  this.countryId = computed(() => this.countryResource.value()?.['@id'], ...(ngDevMode ? [{ debugName: "countryId" }] : []));
12533
- this.logsResource = rxResource({
13042
+ // the score as recorded in the `.jlog`, which is where every new aggregation logs it
13043
+ this.jlogResource = rxResource({
12534
13044
  params: () => ({
12535
13045
  showInfo: this.showInfo(),
12536
13046
  node: this.node()
12537
13047
  }),
12538
- stream: ({ params: { showInfo, node } }) => showInfo
12539
- ? this.nodeService
13048
+ stream: ({ params: { showInfo, node } }) => showInfo ? this.nodeLogsModelsService.getJLog$(node) : of({})
13049
+ });
13050
+ this.jlogLogs = computed(() => jlogScore(this.jlogResource.value() ?? {}), ...(ngDevMode ? [{ debugName: "jlogLogs" }] : []));
13051
+ this.loadingJLog = computed(() => !this.jlogResource.hasValue() || this.jlogResource.isLoading(), ...(ngDevMode ? [{ debugName: "loadingJLog" }] : []));
13052
+ this.useJLog = computed(() => !isEmpty(this.jlogLogs()), ...(ngDevMode ? [{ debugName: "useJLog" }] : []));
13053
+ // only fetch the legacy text logs once we know the `.jlog` does not carry the score
13054
+ this.logsResource = rxResource({
13055
+ params: () => ({
13056
+ skip: !this.showInfo() || this.loadingJLog() || this.useJLog(),
13057
+ node: this.node()
13058
+ }),
13059
+ stream: ({ params: { skip, node } }) => skip
13060
+ ? of({})
13061
+ : this.nodeService
12540
13062
  .getLog$({
12541
13063
  '@type': node['@type'],
12542
13064
  '@id': node['@id']
12543
13065
  })
12544
13066
  .pipe(map(value => (value ? parseLines(value) : [])), mergeAll(), filter(({ data: { message } }) => !!message), map(({ data: { message } }) => parseMessage(message)), filter(({ id }) => id === this.node()['@id']), reduce((a, b) => ({ ...a, ...b }), {}))
12545
- : of({})
12546
13067
  });
12547
- this.logs = computed(() => this.logsResource.value() ?? {}, ...(ngDevMode ? [{ debugName: "logs" }] : []));
12548
- this.validScores = computed(() => Object.fromEntries(Object.entries(isScoreValid).map(([key, value]) => [key, value(this.logs())])), ...(ngDevMode ? [{ debugName: "validScores" }] : []));
13068
+ this.logs = computed(() => this.useJLog() ? this.jlogLogs() : (this.logsResource.value() ?? {}), ...(ngDevMode ? [{ debugName: "logs" }] : []));
13069
+ this.validScores = computed(() => toValidScores(this.logs()), ...(ngDevMode ? [{ debugName: "validScores" }] : []));
12549
13070
  this.minObservations = computed(() => +this.logs().min_nb_observations || defaultMinNbObservations, ...(ngDevMode ? [{ debugName: "minObservations" }] : []));
12550
13071
  this.observations = computed(() => +this.logs().nb_observations, ...(ngDevMode ? [{ debugName: "observations" }] : []));
12551
13072
  this.missingEmissionIds = computed(() => this.logs().missing_emissions?.split(';') ?? [], ...(ngDevMode ? [{ debugName: "missingEmissionIds" }] : []));
@@ -12591,10 +13112,16 @@ class NodeAggregatedQualityScoreComponent {
12591
13112
  this.isGlobal = computed(() => this.countryId()?.startsWith('region-'), ...(ngDevMode ? [{ debugName: "isGlobal" }] : []));
12592
13113
  this.schemaBaseUrl = computed(() => [schemaBaseUrl(), this.node()?.['@type']].join('/'), ...(ngDevMode ? [{ debugName: "schemaBaseUrl" }] : []));
12593
13114
  this.schemaUrl = computed(() => `${this.schemaBaseUrl()}#aggregatedQualityScore`, ...(ngDevMode ? [{ debugName: "schemaUrl" }] : []));
12594
- this.hasProductionQuantity = computed(() => this.logs()?.region_production_quantity !== 'None', ...(ngDevMode ? [{ debugName: "hasProductionQuantity" }] : []));
13115
+ this.hasFaostatYield = computed(() => hasLogValue(this.logs()?.faostat_yield), ...(ngDevMode ? [{ debugName: "hasFaostatYield" }] : []));
13116
+ this.hasProductionQuantity = computed(() => hasLogValue(this.logs()?.region_production_quantity), ...(ngDevMode ? [{ debugName: "hasProductionQuantity" }] : []));
12595
13117
  this.regionProductionQuantity = computed(() => +this.logs()?.region_production_quantity, ...(ngDevMode ? [{ debugName: "regionProductionQuantity" }] : []));
12596
13118
  this.countriesProductionQuantity = computed(() => +this.logs()?.countries_production_quantity, ...(ngDevMode ? [{ debugName: "countriesProductionQuantity" }] : []));
12597
- this.loading = computed(() => [this.logsResource.isLoading(), this.countryResource.isLoading(), this.missingEmissionsResource.isLoading()].some(Boolean), ...(ngDevMode ? [{ debugName: "loading" }] : []));
13119
+ this.loading = computed(() => [
13120
+ this.jlogResource.isLoading(),
13121
+ this.logsResource.isLoading(),
13122
+ this.countryResource.isLoading(),
13123
+ this.missingEmissionsResource.isLoading()
13124
+ ].some(Boolean), ...(ngDevMode ? [{ debugName: "loading" }] : []));
12598
13125
  }
12599
13126
  get hidden() {
12600
13127
  return isUndefined(this.score());
@@ -12606,7 +13133,7 @@ class NodeAggregatedQualityScoreComponent {
12606
13133
  return term['@id'];
12607
13134
  }
12608
13135
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeAggregatedQualityScoreComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
12609
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeAggregatedQualityScoreComponent, isStandalone: true, selector: "he-node-aggregated-quality-score", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, country: { classPropertyName: "country", publicName: "country", isSignal: true, isRequired: false, transformFunction: null }, showInfo: { classPropertyName: "showInfo", publicName: "showInfo", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.is-none": "this.hidden" } }, ngImport: i0, template: "@if (score() >= 0) {\n @switch (mode()) {\n @case ('tag') {\n <div class=\"tags has-addons\">\n <a class=\"tag\" [href]=\"schemaUrl()\" target=\"_blank\">Data Quality</a>\n <ng-container *ngTemplateOutlet=\"quality\" />\n @if (showInfo()) {\n <span\n class=\"tag pointer\"\n [ngbTooltip]=\"logsTooltip\"\n [placement]=\"placement() + ' auto'\"\n triggers=\"click\"\n container=\"body\"\n autoClose=\"outside\"\n tooltipClass=\"quality-tooltip\">\n @if (loading()) {\n <he-svg-icon name=\"loading\" animation=\"spin\" />\n } @else if (logs()) {\n <he-svg-icon name=\"help-circle\" />\n }\n </span>\n }\n </div>\n }\n @default {\n <ng-container *ngTemplateOutlet=\"quality\" />\n }\n }\n}\n\n<ng-template #quality>\n <span\n [ngStyle]=\"{\n 'background-color': scoreColor()\n }\"\n class=\"tag-score | tag is-primary has-text-white has-text-weight-semibold\">\n <span>{{ scoreLevel() | uppercase }}</span>\n </span>\n</ng-template>\n\n<ng-template #logsTooltip>\n <div>\n <p class=\"mb-2\">\n Up to {{ scoreMax() }} points are awarded for the following criteria, where \"low\" is 0 to\n {{ scoreMax() - 3 }} points, \"medium\" is {{ scoreMax() - 2 }} to {{ scoreMax() - 1 }} points, and \"high\" is\n {{ scoreMax() }} points:\n </p>\n @if (validScores()) {\n <div class=\"table-container\">\n <table class=\"table is-fullwidth has-text-white has-background-secondary\">\n <thead>\n <tr>\n <th class=\"has-text-white\">Condition</th>\n <th class=\"has-text-white\">Met</th>\n @if (isCycle()) {\n <th class=\"has-text-white\">Details</th>\n }\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>No aggregated emissions included in the system boundary are missing</td>\n <td [class.has-text-success]=\"validScores()[0]\" [class.has-text-danger]=\"!validScores()[0]\">\n {{ validScores()[0] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (missingEmissions()?.length) {\n <span>{{ missingEmissions().length }}</span>\n <span class=\"is-pl-1\">{{ 'emission' | pluralize: missingEmissions().length }} are missing:</span>\n <div class=\"is-mt-2 | missing-emissions\">\n <ul class=\"is-list-style-disc is-pl-4\">\n @for (term of missingEmissions(); track trackByTerm($index, term); let l = $last) {\n <li>\n <he-node-link class=\"is-inline-block is-pl-1\" linkClass=\"is-dark\" [node]=\"term\">\n <span class=\"is-nowrap has-text-ellipsis\">{{ term.name }}</span>\n </he-node-link>\n </li>\n }\n </ul>\n </div>\n } @else if (logs()?.included_emissions) {\n <span>\n All {{ logs().included_emissions }} {{ 'emission' | pluralize: +logs().included_emissions }} for\n this product are included.\n </span>\n }\n </td>\n }\n </tr>\n <tr>\n <td>The aggregation is based on data from over {{ minObservations() }} Cycles</td>\n <td [class.has-text-success]=\"validScores()[1]\" [class.has-text-danger]=\"!validScores()[1]\">\n {{ validScores()[1] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n <span>Based on {{ observations() }} {{ 'Cycle' | pluralize: observations() }}.</span>\n </td>\n }\n </tr>\n @if (isCrop()) {\n <tr>\n <td>\n The difference between yield per hectare here and FAOSTAT is less than \u00B1{{\n logs().yield_delta_min || logs().delta_min | number\n }}%\n </td>\n <td [class.has-text-success]=\"validScores()[2]\" [class.has-text-danger]=\"!validScores()[2]\">\n {{ validScores()[2] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (logs()?.faostat_yield !== 'None') {\n <span>FAOSTAT yield =</span>\n <span class=\"is-pl-1\">{{ logs()?.faostat_yield | precision: 3 }}</span>\n <span class=\"is-pl-1\">kg/ha;</span>\n <span class=\"is-pl-1\">Aggregated data yield =</span>\n <span class=\"is-pl-1\">{{ logs()?.product_yield | precision: 3 }}</span>\n <span class=\"is-pl-1\">kg/ha.</span>\n } @else {\n No yield found on FAOSTAT for this product.\n }\n </td>\n }\n </tr>\n }\n <tr>\n <td>\n Data completeness is\n <code class=\"is-p-1\">true</code>\n for all priority areas\n </td>\n <td [class.has-text-success]=\"validScores()[3]\" [class.has-text-danger]=\"!validScores()[3]\">\n {{ validScores()[3] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (missingCompletenessFields()?.length) {\n <span>{{ missingCompletenessFields().length }}</span>\n <span class=\"is-pl-1\">completeness areas missing data:</span>\n @for (field of missingCompletenessFields(); track field; let l = $last) {\n <a class=\"is-inline-block is-pl-1 is-dark\" [href]=\"completenessUrl(field)\" target=\"_blank\">\n {{ field }}\n </a>\n <span>{{ l ? '.' : ',' }}</span>\n }\n }\n </td>\n }\n </tr>\n @if (isCrop() && isGlobal()) {\n <tr>\n <td>Aggregated countries represent at least 75% of {{ countryName() }} production</td>\n <td [class.has-text-success]=\"validScores()[4]\" [class.has-text-danger]=\"!validScores()[4]\">\n {{ validScores()[4] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (hasProductionQuantity()) {\n <span>{{ countryName() }} production quantity =</span>\n <span class=\"is-pl-1\">{{ regionProductionQuantity() / 1000000 | number: '1.0-0' }}</span>\n <span class=\"is-pl-1\">million tonnes;</span>\n <span class=\"is-pl-1\">Production of countries included in the aggregation =</span>\n <span class=\"is-pl-1\">{{ countriesProductionQuantity() / 1000000 | number: '1.0-0' }}</span>\n <span class=\"is-pl-1\">million tonnes.</span>\n } @else {\n No production quantity found on FAOSTAT for this product.\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n</ng-template>\n", styles: [":host{display:inline-block}::ng-deep .quality-tooltip{min-width:500px}.missing-emissions{max-height:250px;overflow-y:auto}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: NodeLinkComponent, selector: "he-node-link", inputs: ["node", "dataState", "showExternalLink", "linkClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: NgbTooltipModule }, { kind: "directive", type: i1$1.NgbTooltip, selector: "[ngbTooltip]", inputs: ["animation", "autoClose", "placement", "popperOptions", "triggers", "positionTarget", "container", "disableTooltip", "tooltipClass", "tooltipContext", "openDelay", "closeDelay", "ngbTooltip"], outputs: ["shown", "hidden"], exportAs: ["ngbTooltip"] }, { kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "pipe", type: PluralizePipe, name: "pluralize" }, { kind: "pipe", type: DecimalPipe, name: "number" }, { kind: "pipe", type: PrecisionPipe, name: "precision" }, { kind: "pipe", type: UpperCasePipe, name: "uppercase" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
13136
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeAggregatedQualityScoreComponent, isStandalone: true, selector: "he-node-aggregated-quality-score", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, country: { classPropertyName: "country", publicName: "country", isSignal: true, isRequired: false, transformFunction: null }, showInfo: { classPropertyName: "showInfo", publicName: "showInfo", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.is-none": "this.hidden" } }, ngImport: i0, template: "@if (score() >= 0) {\n @switch (mode()) {\n @case ('tag') {\n <div class=\"tags has-addons\">\n <a class=\"tag\" [href]=\"schemaUrl()\" target=\"_blank\">Data Quality</a>\n <ng-container *ngTemplateOutlet=\"quality\" />\n @if (showInfo()) {\n <span\n class=\"tag pointer\"\n [ngbTooltip]=\"logsTooltip\"\n [placement]=\"placement() + ' auto'\"\n triggers=\"click\"\n container=\"body\"\n autoClose=\"outside\"\n tooltipClass=\"quality-tooltip\">\n @if (loading()) {\n <he-svg-icon name=\"loading\" animation=\"spin\" />\n } @else if (logs()) {\n <he-svg-icon name=\"help-circle\" />\n }\n </span>\n }\n </div>\n }\n @default {\n <ng-container *ngTemplateOutlet=\"quality\" />\n }\n }\n}\n\n<ng-template #quality>\n <span\n [ngStyle]=\"{\n 'background-color': scoreColor()\n }\"\n class=\"tag-score | tag is-primary has-text-white has-text-weight-semibold\">\n <span>{{ scoreLevel() | uppercase }}</span>\n </span>\n</ng-template>\n\n<ng-template #logsTooltip>\n <div>\n <p class=\"mb-2\">\n Up to {{ scoreMax() }} points are awarded for the following criteria, where \"low\" is 0 to\n {{ scoreMax() - 3 }} points, \"medium\" is {{ scoreMax() - 2 }} to {{ scoreMax() - 1 }} points, and \"high\" is\n {{ scoreMax() }} points:\n </p>\n @if (validScores()) {\n <div class=\"table-container\">\n <table class=\"table is-fullwidth has-text-white has-background-secondary\">\n <thead>\n <tr>\n <th class=\"has-text-white\">Condition</th>\n <th class=\"has-text-white\">Met</th>\n @if (isCycle()) {\n <th class=\"has-text-white\">Details</th>\n }\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>No aggregated emissions included in the system boundary are missing</td>\n <td [class.has-text-success]=\"validScores()[0]\" [class.has-text-danger]=\"!validScores()[0]\">\n {{ validScores()[0] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (missingEmissions()?.length) {\n <span>{{ missingEmissions().length }}</span>\n <span class=\"is-pl-1\">{{ 'emission' | pluralize: missingEmissions().length }} are missing:</span>\n <div class=\"is-mt-2 | missing-emissions\">\n <ul class=\"is-list-style-disc is-pl-4\">\n @for (term of missingEmissions(); track trackByTerm($index, term); let l = $last) {\n <li>\n <he-node-link class=\"is-inline-block is-pl-1\" linkClass=\"is-dark\" [node]=\"term\">\n <span class=\"is-nowrap has-text-ellipsis\">{{ term.name }}</span>\n </he-node-link>\n </li>\n }\n </ul>\n </div>\n } @else if (logs()?.included_emissions) {\n <span>\n All {{ logs().included_emissions }} {{ 'emission' | pluralize: +logs().included_emissions }} for\n this product are included.\n </span>\n }\n </td>\n }\n </tr>\n <tr>\n <td>The aggregation is based on data from over {{ minObservations() }} Cycles</td>\n <td [class.has-text-success]=\"validScores()[1]\" [class.has-text-danger]=\"!validScores()[1]\">\n {{ validScores()[1] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n <span>Based on {{ observations() }} {{ 'Cycle' | pluralize: observations() }}.</span>\n </td>\n }\n </tr>\n @if (isCrop()) {\n <tr>\n <td>\n The difference between yield per hectare here and FAOSTAT is less than \u00B1{{\n logs().yield_delta_min || logs().delta_min | number\n }}%\n </td>\n <td [class.has-text-success]=\"validScores()[2]\" [class.has-text-danger]=\"!validScores()[2]\">\n {{ validScores()[2] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (hasFaostatYield()) {\n <span>FAOSTAT yield =</span>\n <span class=\"is-pl-1\">{{ logs()?.faostat_yield | precision: 3 }}</span>\n <span class=\"is-pl-1\">kg/ha;</span>\n <span class=\"is-pl-1\">Aggregated data yield =</span>\n <span class=\"is-pl-1\">{{ logs()?.product_yield | precision: 3 }}</span>\n <span class=\"is-pl-1\">kg/ha.</span>\n } @else {\n No yield found on FAOSTAT for this product.\n }\n </td>\n }\n </tr>\n }\n <tr>\n <td>\n Data completeness is\n <code class=\"is-p-1\">true</code>\n for all priority areas\n </td>\n <td [class.has-text-success]=\"validScores()[3]\" [class.has-text-danger]=\"!validScores()[3]\">\n {{ validScores()[3] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (missingCompletenessFields()?.length) {\n <span>{{ missingCompletenessFields().length }}</span>\n <span class=\"is-pl-1\">completeness areas missing data:</span>\n @for (field of missingCompletenessFields(); track field; let l = $last) {\n <a class=\"is-inline-block is-pl-1 is-dark\" [href]=\"completenessUrl(field)\" target=\"_blank\">\n {{ field }}\n </a>\n <span>{{ l ? '.' : ',' }}</span>\n }\n }\n </td>\n }\n </tr>\n @if (isCrop() && isGlobal()) {\n <tr>\n <td>Aggregated countries represent at least 75% of {{ countryName() }} production</td>\n <td [class.has-text-success]=\"validScores()[4]\" [class.has-text-danger]=\"!validScores()[4]\">\n {{ validScores()[4] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (hasProductionQuantity()) {\n <span>{{ countryName() }} production quantity =</span>\n <span class=\"is-pl-1\">{{ regionProductionQuantity() / 1000000 | number: '1.0-0' }}</span>\n <span class=\"is-pl-1\">million tonnes;</span>\n <span class=\"is-pl-1\">Production of countries included in the aggregation =</span>\n <span class=\"is-pl-1\">{{ countriesProductionQuantity() / 1000000 | number: '1.0-0' }}</span>\n <span class=\"is-pl-1\">million tonnes.</span>\n } @else {\n No production quantity found on FAOSTAT for this product.\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n</ng-template>\n", styles: [":host{display:inline-block}::ng-deep .quality-tooltip{min-width:500px}.missing-emissions{max-height:250px;overflow-y:auto}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: NodeLinkComponent, selector: "he-node-link", inputs: ["node", "dataState", "showExternalLink", "linkClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: NgbTooltipModule }, { kind: "directive", type: i1$1.NgbTooltip, selector: "[ngbTooltip]", inputs: ["animation", "autoClose", "placement", "popperOptions", "triggers", "positionTarget", "container", "disableTooltip", "tooltipClass", "tooltipContext", "openDelay", "closeDelay", "ngbTooltip"], outputs: ["shown", "hidden"], exportAs: ["ngbTooltip"] }, { kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "pipe", type: PluralizePipe, name: "pluralize" }, { kind: "pipe", type: DecimalPipe, name: "number" }, { kind: "pipe", type: PrecisionPipe, name: "precision" }, { kind: "pipe", type: UpperCasePipe, name: "uppercase" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
12610
13137
  }
12611
13138
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeAggregatedQualityScoreComponent, decorators: [{
12612
13139
  type: Component$1,
@@ -12620,7 +13147,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
12620
13147
  NgTemplateOutlet,
12621
13148
  NgbTooltipModule,
12622
13149
  HESvgIconComponent
12623
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (score() >= 0) {\n @switch (mode()) {\n @case ('tag') {\n <div class=\"tags has-addons\">\n <a class=\"tag\" [href]=\"schemaUrl()\" target=\"_blank\">Data Quality</a>\n <ng-container *ngTemplateOutlet=\"quality\" />\n @if (showInfo()) {\n <span\n class=\"tag pointer\"\n [ngbTooltip]=\"logsTooltip\"\n [placement]=\"placement() + ' auto'\"\n triggers=\"click\"\n container=\"body\"\n autoClose=\"outside\"\n tooltipClass=\"quality-tooltip\">\n @if (loading()) {\n <he-svg-icon name=\"loading\" animation=\"spin\" />\n } @else if (logs()) {\n <he-svg-icon name=\"help-circle\" />\n }\n </span>\n }\n </div>\n }\n @default {\n <ng-container *ngTemplateOutlet=\"quality\" />\n }\n }\n}\n\n<ng-template #quality>\n <span\n [ngStyle]=\"{\n 'background-color': scoreColor()\n }\"\n class=\"tag-score | tag is-primary has-text-white has-text-weight-semibold\">\n <span>{{ scoreLevel() | uppercase }}</span>\n </span>\n</ng-template>\n\n<ng-template #logsTooltip>\n <div>\n <p class=\"mb-2\">\n Up to {{ scoreMax() }} points are awarded for the following criteria, where \"low\" is 0 to\n {{ scoreMax() - 3 }} points, \"medium\" is {{ scoreMax() - 2 }} to {{ scoreMax() - 1 }} points, and \"high\" is\n {{ scoreMax() }} points:\n </p>\n @if (validScores()) {\n <div class=\"table-container\">\n <table class=\"table is-fullwidth has-text-white has-background-secondary\">\n <thead>\n <tr>\n <th class=\"has-text-white\">Condition</th>\n <th class=\"has-text-white\">Met</th>\n @if (isCycle()) {\n <th class=\"has-text-white\">Details</th>\n }\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>No aggregated emissions included in the system boundary are missing</td>\n <td [class.has-text-success]=\"validScores()[0]\" [class.has-text-danger]=\"!validScores()[0]\">\n {{ validScores()[0] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (missingEmissions()?.length) {\n <span>{{ missingEmissions().length }}</span>\n <span class=\"is-pl-1\">{{ 'emission' | pluralize: missingEmissions().length }} are missing:</span>\n <div class=\"is-mt-2 | missing-emissions\">\n <ul class=\"is-list-style-disc is-pl-4\">\n @for (term of missingEmissions(); track trackByTerm($index, term); let l = $last) {\n <li>\n <he-node-link class=\"is-inline-block is-pl-1\" linkClass=\"is-dark\" [node]=\"term\">\n <span class=\"is-nowrap has-text-ellipsis\">{{ term.name }}</span>\n </he-node-link>\n </li>\n }\n </ul>\n </div>\n } @else if (logs()?.included_emissions) {\n <span>\n All {{ logs().included_emissions }} {{ 'emission' | pluralize: +logs().included_emissions }} for\n this product are included.\n </span>\n }\n </td>\n }\n </tr>\n <tr>\n <td>The aggregation is based on data from over {{ minObservations() }} Cycles</td>\n <td [class.has-text-success]=\"validScores()[1]\" [class.has-text-danger]=\"!validScores()[1]\">\n {{ validScores()[1] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n <span>Based on {{ observations() }} {{ 'Cycle' | pluralize: observations() }}.</span>\n </td>\n }\n </tr>\n @if (isCrop()) {\n <tr>\n <td>\n The difference between yield per hectare here and FAOSTAT is less than \u00B1{{\n logs().yield_delta_min || logs().delta_min | number\n }}%\n </td>\n <td [class.has-text-success]=\"validScores()[2]\" [class.has-text-danger]=\"!validScores()[2]\">\n {{ validScores()[2] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (logs()?.faostat_yield !== 'None') {\n <span>FAOSTAT yield =</span>\n <span class=\"is-pl-1\">{{ logs()?.faostat_yield | precision: 3 }}</span>\n <span class=\"is-pl-1\">kg/ha;</span>\n <span class=\"is-pl-1\">Aggregated data yield =</span>\n <span class=\"is-pl-1\">{{ logs()?.product_yield | precision: 3 }}</span>\n <span class=\"is-pl-1\">kg/ha.</span>\n } @else {\n No yield found on FAOSTAT for this product.\n }\n </td>\n }\n </tr>\n }\n <tr>\n <td>\n Data completeness is\n <code class=\"is-p-1\">true</code>\n for all priority areas\n </td>\n <td [class.has-text-success]=\"validScores()[3]\" [class.has-text-danger]=\"!validScores()[3]\">\n {{ validScores()[3] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (missingCompletenessFields()?.length) {\n <span>{{ missingCompletenessFields().length }}</span>\n <span class=\"is-pl-1\">completeness areas missing data:</span>\n @for (field of missingCompletenessFields(); track field; let l = $last) {\n <a class=\"is-inline-block is-pl-1 is-dark\" [href]=\"completenessUrl(field)\" target=\"_blank\">\n {{ field }}\n </a>\n <span>{{ l ? '.' : ',' }}</span>\n }\n }\n </td>\n }\n </tr>\n @if (isCrop() && isGlobal()) {\n <tr>\n <td>Aggregated countries represent at least 75% of {{ countryName() }} production</td>\n <td [class.has-text-success]=\"validScores()[4]\" [class.has-text-danger]=\"!validScores()[4]\">\n {{ validScores()[4] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (hasProductionQuantity()) {\n <span>{{ countryName() }} production quantity =</span>\n <span class=\"is-pl-1\">{{ regionProductionQuantity() / 1000000 | number: '1.0-0' }}</span>\n <span class=\"is-pl-1\">million tonnes;</span>\n <span class=\"is-pl-1\">Production of countries included in the aggregation =</span>\n <span class=\"is-pl-1\">{{ countriesProductionQuantity() / 1000000 | number: '1.0-0' }}</span>\n <span class=\"is-pl-1\">million tonnes.</span>\n } @else {\n No production quantity found on FAOSTAT for this product.\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n</ng-template>\n", styles: [":host{display:inline-block}::ng-deep .quality-tooltip{min-width:500px}.missing-emissions{max-height:250px;overflow-y:auto}\n"] }]
13150
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (score() >= 0) {\n @switch (mode()) {\n @case ('tag') {\n <div class=\"tags has-addons\">\n <a class=\"tag\" [href]=\"schemaUrl()\" target=\"_blank\">Data Quality</a>\n <ng-container *ngTemplateOutlet=\"quality\" />\n @if (showInfo()) {\n <span\n class=\"tag pointer\"\n [ngbTooltip]=\"logsTooltip\"\n [placement]=\"placement() + ' auto'\"\n triggers=\"click\"\n container=\"body\"\n autoClose=\"outside\"\n tooltipClass=\"quality-tooltip\">\n @if (loading()) {\n <he-svg-icon name=\"loading\" animation=\"spin\" />\n } @else if (logs()) {\n <he-svg-icon name=\"help-circle\" />\n }\n </span>\n }\n </div>\n }\n @default {\n <ng-container *ngTemplateOutlet=\"quality\" />\n }\n }\n}\n\n<ng-template #quality>\n <span\n [ngStyle]=\"{\n 'background-color': scoreColor()\n }\"\n class=\"tag-score | tag is-primary has-text-white has-text-weight-semibold\">\n <span>{{ scoreLevel() | uppercase }}</span>\n </span>\n</ng-template>\n\n<ng-template #logsTooltip>\n <div>\n <p class=\"mb-2\">\n Up to {{ scoreMax() }} points are awarded for the following criteria, where \"low\" is 0 to\n {{ scoreMax() - 3 }} points, \"medium\" is {{ scoreMax() - 2 }} to {{ scoreMax() - 1 }} points, and \"high\" is\n {{ scoreMax() }} points:\n </p>\n @if (validScores()) {\n <div class=\"table-container\">\n <table class=\"table is-fullwidth has-text-white has-background-secondary\">\n <thead>\n <tr>\n <th class=\"has-text-white\">Condition</th>\n <th class=\"has-text-white\">Met</th>\n @if (isCycle()) {\n <th class=\"has-text-white\">Details</th>\n }\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>No aggregated emissions included in the system boundary are missing</td>\n <td [class.has-text-success]=\"validScores()[0]\" [class.has-text-danger]=\"!validScores()[0]\">\n {{ validScores()[0] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (missingEmissions()?.length) {\n <span>{{ missingEmissions().length }}</span>\n <span class=\"is-pl-1\">{{ 'emission' | pluralize: missingEmissions().length }} are missing:</span>\n <div class=\"is-mt-2 | missing-emissions\">\n <ul class=\"is-list-style-disc is-pl-4\">\n @for (term of missingEmissions(); track trackByTerm($index, term); let l = $last) {\n <li>\n <he-node-link class=\"is-inline-block is-pl-1\" linkClass=\"is-dark\" [node]=\"term\">\n <span class=\"is-nowrap has-text-ellipsis\">{{ term.name }}</span>\n </he-node-link>\n </li>\n }\n </ul>\n </div>\n } @else if (logs()?.included_emissions) {\n <span>\n All {{ logs().included_emissions }} {{ 'emission' | pluralize: +logs().included_emissions }} for\n this product are included.\n </span>\n }\n </td>\n }\n </tr>\n <tr>\n <td>The aggregation is based on data from over {{ minObservations() }} Cycles</td>\n <td [class.has-text-success]=\"validScores()[1]\" [class.has-text-danger]=\"!validScores()[1]\">\n {{ validScores()[1] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n <span>Based on {{ observations() }} {{ 'Cycle' | pluralize: observations() }}.</span>\n </td>\n }\n </tr>\n @if (isCrop()) {\n <tr>\n <td>\n The difference between yield per hectare here and FAOSTAT is less than \u00B1{{\n logs().yield_delta_min || logs().delta_min | number\n }}%\n </td>\n <td [class.has-text-success]=\"validScores()[2]\" [class.has-text-danger]=\"!validScores()[2]\">\n {{ validScores()[2] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (hasFaostatYield()) {\n <span>FAOSTAT yield =</span>\n <span class=\"is-pl-1\">{{ logs()?.faostat_yield | precision: 3 }}</span>\n <span class=\"is-pl-1\">kg/ha;</span>\n <span class=\"is-pl-1\">Aggregated data yield =</span>\n <span class=\"is-pl-1\">{{ logs()?.product_yield | precision: 3 }}</span>\n <span class=\"is-pl-1\">kg/ha.</span>\n } @else {\n No yield found on FAOSTAT for this product.\n }\n </td>\n }\n </tr>\n }\n <tr>\n <td>\n Data completeness is\n <code class=\"is-p-1\">true</code>\n for all priority areas\n </td>\n <td [class.has-text-success]=\"validScores()[3]\" [class.has-text-danger]=\"!validScores()[3]\">\n {{ validScores()[3] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (missingCompletenessFields()?.length) {\n <span>{{ missingCompletenessFields().length }}</span>\n <span class=\"is-pl-1\">completeness areas missing data:</span>\n @for (field of missingCompletenessFields(); track field; let l = $last) {\n <a class=\"is-inline-block is-pl-1 is-dark\" [href]=\"completenessUrl(field)\" target=\"_blank\">\n {{ field }}\n </a>\n <span>{{ l ? '.' : ',' }}</span>\n }\n }\n </td>\n }\n </tr>\n @if (isCrop() && isGlobal()) {\n <tr>\n <td>Aggregated countries represent at least 75% of {{ countryName() }} production</td>\n <td [class.has-text-success]=\"validScores()[4]\" [class.has-text-danger]=\"!validScores()[4]\">\n {{ validScores()[4] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (hasProductionQuantity()) {\n <span>{{ countryName() }} production quantity =</span>\n <span class=\"is-pl-1\">{{ regionProductionQuantity() / 1000000 | number: '1.0-0' }}</span>\n <span class=\"is-pl-1\">million tonnes;</span>\n <span class=\"is-pl-1\">Production of countries included in the aggregation =</span>\n <span class=\"is-pl-1\">{{ countriesProductionQuantity() / 1000000 | number: '1.0-0' }}</span>\n <span class=\"is-pl-1\">million tonnes.</span>\n } @else {\n No production quantity found on FAOSTAT for this product.\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n</ng-template>\n", styles: [":host{display:inline-block}::ng-deep .quality-tooltip{min-width:500px}.missing-emissions{max-height:250px;overflow-y:auto}\n"] }]
12624
13151
  }], propDecorators: { hidden: [{
12625
13152
  type: HostBinding,
12626
13153
  args: ['class.is-none']
@@ -14211,6 +14738,71 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
14211
14738
  args: [{ selector: 'he-sites-maps', changeDetection: ChangeDetectionStrategy.OnPush, imports: [GoogleMap, MapMarker, MapPolygon, AsyncPipe], template: "@if (mapLoaded$ | async) {\n <google-map (mapInitialized)=\"mapLoaded()\" height=\"100%\" width=\"100%\" [zoom]=\"zoom()\" [center]=\"mapCenter()\">\n @for (marker of markers(); track trackByMarker($index, marker)) {\n <map-marker [position]=\"marker.position\" [icon]=\"marker.icon\"></map-marker>\n }\n @for (polygon of sitePolygons(); track trackByPolygon($index, polygon)) {\n <map-polygon [paths]=\"polygon.paths\" [options]=\"polygon.options\" />\n }\n @for (polygon of termPolygons(); track trackByPolygon($index, polygon)) {\n <map-polygon [paths]=\"polygon.paths\" [options]=\"polygon.options\" />\n }\n </google-map>\n}\n\n@if (showNotice()) {\n <p class=\"mt-2 is-italic is-size-7\">The information provided might not be complete</p>\n}\n\n@if (showNoLocation()) {\n <div class=\"no-location has-text-center has-text-light\">\n <span>No precise location data</span>\n </div>\n}\n", styles: [":host{display:block;height:100%;position:relative;width:100%}.no-location{background-color:#0000004d;left:0;height:100%;position:absolute;top:0;width:100%;z-index:9}.no-location>span{display:inline-block;margin-top:12%}\n"] }]
14212
14739
  }], ctorParameters: () => [], propDecorators: { map: [{ type: i0.ViewChild, args: [i0.forwardRef(() => GoogleMap), { isSignal: true }] }], loadPolygons: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadPolygons", required: false }] }], sites: [{ type: i0.Input, args: [{ isSignal: true, alias: "sites", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }], showNotice: [{ type: i0.Input, args: [{ isSignal: true, alias: "showNotice", required: false }] }] } });
14213
14740
 
14741
+ /**
14742
+ * The distribution behind an `is outside confidence interval` warning, shown on demand.
14743
+ *
14744
+ * The chart cannot be part of the error message itself: the catalog is framework-agnostic and its
14745
+ * output is injected with `[innerHTML]`, which Angular sanitizes — canvas and inline SVG are
14746
+ * stripped. So the message stays text and this sits beside it.
14747
+ */
14748
+ class FilesErrorDistributionComponent {
14749
+ constructor() {
14750
+ this.error = input(...(ngDevMode ? [undefined, { debugName: "error" }] : []));
14751
+ this.params = computed(() => this.error()?.params, ...(ngDevMode ? [{ debugName: "params" }] : []));
14752
+ /**
14753
+ * `mu`/`sd` are only sent by the validation service from `@hestia-earth/data-validation` 0.41.1
14754
+ * on. Older warnings carry `min`/`max` alone, which cannot reconstruct the curve — `min` is
14755
+ * clamped at 0 — so the button is hidden rather than opening onto a wrong distribution.
14756
+ */
14757
+ this.available = computed(() => Number.isFinite(this.params()?.mu) && Number.isFinite(this.params()?.sd) && this.params()?.sd > 0, ...(ngDevMode ? [{ debugName: "available" }] : []));
14758
+ /** What was checked: the grouped input key where there is one, else the term itself. */
14759
+ this.label = computed(() => this.params()?.group || this.params()?.term?.name, ...(ngDevMode ? [{ debugName: "label" }] : []));
14760
+ this.country = computed(() => this.params()?.country?.name, ...(ngDevMode ? [{ debugName: "country" }] : []));
14761
+ /**
14762
+ * The reported value to mark. `outliers` holds every value that fell outside for this field —
14763
+ * in practice one, since each warning is raised per value.
14764
+ */
14765
+ this.value = computed(() => this.params()?.outliers?.[0], ...(ngDevMode ? [{ debugName: "value" }] : []));
14766
+ /** `threshold` is the share of the data the shaded interval covers, e.g. `0.95` → `95%`. */
14767
+ this.thresholdPercent = computed(() => Number.isFinite(this.params()?.threshold) ? this.params().threshold * 100 : null, ...(ngDevMode ? [{ debugName: "thresholdPercent" }] : []));
14768
+ this.isPrior = computed(() => this.params()?.source === 'prior', ...(ngDevMode ? [{ debugName: "isPrior" }] : []));
14769
+ /**
14770
+ * The warning is always raised on an Input or Product `value` — a physical amount, which cannot
14771
+ * be negative however far the fitted normal's left tail reaches. The validation service clamps
14772
+ * `min` at 0 for the same reason.
14773
+ */
14774
+ this.minX = 0;
14775
+ /**
14776
+ * The popover body is the platform's dark blue (`$blue`), so the axis has to be drawn light on
14777
+ * it. The chart's own colours are background-agnostic; only the axis needs saying.
14778
+ */
14779
+ this.chartConfig = {
14780
+ options: {
14781
+ scales: {
14782
+ x: {
14783
+ ticks: { color: '#fff' },
14784
+ title: { color: '#fff' },
14785
+ grid: { color: 'rgba(255, 255, 255, 0.15)' },
14786
+ border: { color: 'rgba(255, 255, 255, 0.3)' }
14787
+ }
14788
+ }
14789
+ }
14790
+ };
14791
+ }
14792
+ get hidden() {
14793
+ return !this.available();
14794
+ }
14795
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: FilesErrorDistributionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
14796
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: FilesErrorDistributionComponent, isStandalone: true, selector: "he-files-error-distribution", inputs: { error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.is-none": "this.hidden" } }, ngImport: i0, template: "@if (available()) {\n <a\n class=\"is-nowrap\"\n [ngbPopover]=\"distributionPopover\"\n [popoverTitle]=\"'Distribution for ' + label()\"\n triggers=\"click\"\n autoClose=\"outside\"\n placement=\"bottom auto\"\n popoverClass=\"he-distribution-popover\"\n container=\"body\">\n <he-svg-icon name=\"chart\" size=\"24\" />\n <span class=\"is-pl-2\">Show distribution</span>\n </a>\n}\n\n<ng-template #distributionPopover>\n <div>\n <he-distribution-chart\n [mu]=\"params().mu\"\n [sd]=\"params().sd\"\n [intervalMin]=\"params().min\"\n [intervalMax]=\"params().max\"\n [value]=\"value()\"\n [label]=\"label()\"\n [minX]=\"minX\"\n [config]=\"chartConfig\" />\n\n <p class=\"is-size-7\">\n How common each value of\n <b>{{ label() }}</b>\n is\n @if (country()) {\n in\n <b>{{ country() }}</b>\n }\n on the HESTIA platform: the taller the curve, the more common the value. It peaks at the average,\n <b>{{ params().mu | precision }}</b>\n <span>.</span>\n </p>\n\n <p class=\"is-size-7 is-mt-2\">\n The shaded band is the\n @if (thresholdPercent()) {\n {{ thresholdPercent() }}%\n }\n confidence interval, from\n <b>{{ params().min | precision }}</b>\n to\n <b>{{ params().max | precision }}</b>\n <span>. The orange line marks your value.</span>\n </p>\n\n @if (isPrior()) {\n <p class=\"is-size-7 is-mt-2\">\n This is the FAOSTAT prior (national average), not HESTIA Cycle data for this product, so it is weak evidence.\n </p>\n }\n </div>\n</ng-template>\n", styles: [":host{display:flex;flex-grow:0}::ng-deep .he-distribution-popover .popover-body{width:320px;max-width:100%}::ng-deep .he-distribution-popover he-distribution-chart{display:block;height:200px}\n"], dependencies: [{ kind: "ngmodule", type: NgbPopoverModule }, { kind: "directive", type: i1$1.NgbPopover, selector: "[ngbPopover]", inputs: ["animation", "autoClose", "ngbPopover", "popoverTitle", "placement", "popperOptions", "triggers", "positionTarget", "container", "disablePopover", "popoverClass", "popoverContext", "openDelay", "closeDelay"], outputs: ["shown", "hidden"], exportAs: ["ngbPopover"] }, { kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "component", type: DistributionChartComponent, selector: "he-distribution-chart", inputs: ["distribution", "value", "label", "nbBins", "maxPercentile", "config", "mu", "sd", "intervalMin", "intervalMax", "minX"], exportAs: ["distributionChart"] }, { kind: "pipe", type: PrecisionPipe, name: "precision" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
14797
+ }
14798
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: FilesErrorDistributionComponent, decorators: [{
14799
+ type: Component$1,
14800
+ args: [{ selector: 'he-files-error-distribution', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgbPopoverModule, HESvgIconComponent, DistributionChartComponent, PrecisionPipe], template: "@if (available()) {\n <a\n class=\"is-nowrap\"\n [ngbPopover]=\"distributionPopover\"\n [popoverTitle]=\"'Distribution for ' + label()\"\n triggers=\"click\"\n autoClose=\"outside\"\n placement=\"bottom auto\"\n popoverClass=\"he-distribution-popover\"\n container=\"body\">\n <he-svg-icon name=\"chart\" size=\"24\" />\n <span class=\"is-pl-2\">Show distribution</span>\n </a>\n}\n\n<ng-template #distributionPopover>\n <div>\n <he-distribution-chart\n [mu]=\"params().mu\"\n [sd]=\"params().sd\"\n [intervalMin]=\"params().min\"\n [intervalMax]=\"params().max\"\n [value]=\"value()\"\n [label]=\"label()\"\n [minX]=\"minX\"\n [config]=\"chartConfig\" />\n\n <p class=\"is-size-7\">\n How common each value of\n <b>{{ label() }}</b>\n is\n @if (country()) {\n in\n <b>{{ country() }}</b>\n }\n on the HESTIA platform: the taller the curve, the more common the value. It peaks at the average,\n <b>{{ params().mu | precision }}</b>\n <span>.</span>\n </p>\n\n <p class=\"is-size-7 is-mt-2\">\n The shaded band is the\n @if (thresholdPercent()) {\n {{ thresholdPercent() }}%\n }\n confidence interval, from\n <b>{{ params().min | precision }}</b>\n to\n <b>{{ params().max | precision }}</b>\n <span>. The orange line marks your value.</span>\n </p>\n\n @if (isPrior()) {\n <p class=\"is-size-7 is-mt-2\">\n This is the FAOSTAT prior (national average), not HESTIA Cycle data for this product, so it is weak evidence.\n </p>\n }\n </div>\n</ng-template>\n", styles: [":host{display:flex;flex-grow:0}::ng-deep .he-distribution-popover .popover-body{width:320px;max-width:100%}::ng-deep .he-distribution-popover he-distribution-chart{display:block;height:200px}\n"] }]
14801
+ }], propDecorators: { error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], hidden: [{
14802
+ type: HostBinding,
14803
+ args: ['class.is-none']
14804
+ }] } });
14805
+
14214
14806
  const stringify = (value) => JSON.stringify(value);
14215
14807
  const focusFirstGroupError = (element) => {
14216
14808
  const groupError = (element.querySelectorAll('.is-group-error-danger')?.[0] ||
@@ -14331,7 +14923,7 @@ class FilesFormComponent {
14331
14923
  return focusFirstError(this.ref.nativeElement);
14332
14924
  }
14333
14925
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: FilesFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
14334
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: FilesFormComponent, isStandalone: true, selector: "he-files-form", inputs: { isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, showNodeLink: { classPropertyName: "showNodeLink", publicName: "showNodeLink", isSignal: true, isRequired: false, transformFunction: null }, errorGuidePrefix: { classPropertyName: "errorGuidePrefix", publicName: "errorGuidePrefix", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpen: "isOpenChange", nodeErorrResolved: "nodeErorrResolved" }, exportAs: ["filesForm"], ngImport: i0, template: "<div class=\"is-flex is-flex-direction-column is-align-items-flex-start is-align-self-stretch | files-form-container\">\n <div\n class=\"is-flex is-flex-direction-row is-justify-content-space-between is-align-items-center is-align-self-stretch is-gap-16 px-4 py-2 has-text-secondary | files-form-header\"\n (click)=\"isOpen.set(!isOpen())\"\n pointer>\n <div class=\"is-flex is-gap-8 is-align-items-center\">\n @if (nodeProperty()) {\n <he-node-icon class=\"is-flex\" [type]=\"schemaType()\" [size]=\"24\" />\n <span class=\"has-text-weight-bold has-text-secondary is-size-5\">{{ schemaType() }}</span>\n @if (nodeProperty().hasError || hasError()) {\n <he-svg-icon name=\"xmark-circle\" class=\"has-text-danger is-flex is-align-items-center\" />\n <span class=\"has-text-danger has-text-weight-bold is-italic\">Error</span>\n } @else if (nodeProperty().hasWarning || hasWarning()) {\n <he-svg-icon name=\"exclamation-triangle\" class=\"has-text-warning is-flex is-align-items-center\" />\n <span class=\"has-text-warning has-text-weight-bold is-italic\">Warning</span>\n }\n }\n </div>\n @if ((showNodeLink() && nodeUrl()) || canOpen()) {\n <div class=\"is-flex is-flex-direction-row is-align-items-center is-gap-16\">\n @if (showNodeLink() && nodeUrl()) {\n <ng-container *ngTemplateOutlet=\"nodeLink; context: { url: nodeUrl(), nodeType: schemaType() }\" />\n }\n @if (canOpen()) {\n <he-svg-icon [name]=\"isOpen() ? 'minus' : 'plus'\" />\n }\n </div>\n }\n </div>\n\n <div class=\"is-flex is-flex-direction-column is-align-items-flex-start is-align-self-stretch\">\n @if (isOpen()) {\n @if (nodeProperty()?.error) {\n <ng-container\n *ngTemplateOutlet=\"\n propertyError;\n context: { property: nodeProperty(), classes: 'is-my-1 is-py-1 is-px-3' }\n \" />\n }\n }\n\n @if (unmatchedErrors().length) {\n @for (property of unmatchedErrors(); track property.id) {\n <ng-container *ngTemplateOutlet=\"propertyError; context: { property, classes: 'is-my-1 is-py-1 is-px-3' }\" />\n }\n }\n\n <div class=\"is-flex is-flex-direction-row is-flex-wrap-wrap is-align-self-stretch\">\n @for (property of properties(); track trackByProperty($index, property)) {\n <ng-container *ngTemplateOutlet=\"showProperty; context: { $implicit: property }\" />\n }\n </div>\n\n @if (isOpen()) {\n <ng-container *ngTemplateOutlet=\"propertyMap; context: { $implicit: nodeProperty() }\" />\n }\n </div>\n\n <ng-content />\n</div>\n\n<ng-template #propertyKeyTooltip let-property=\"property\">\n <markdown [data]=\"property.schema.description\" />\n</ng-template>\n\n<ng-template #propertyKey let-property=\"property\">\n @if (property.schema?.description) {\n <span\n class=\"trigger-popover | property-key\"\n [ngbTooltip]=\"propertyKeyTooltip\"\n [tooltipContext]=\"{ property }\"\n triggers=\"click\"\n autoClose=\"outside\"\n tooltipClass=\"property-tooltip\"\n placement=\"bottom-left auto\"\n container=\"body\"\n (click)=\"$event.stopPropagation()\">\n <span>{{ property.key }}</span>\n </span>\n } @else {\n <span>{{ property.key }}</span>\n }\n</ng-template>\n\n<ng-template #propertyContent let-property=\"property\">\n <div class=\"is-flex is-align-items-center is-flex-wrap-wrap is-gap-4\">\n <div class=\"is-flex has-text-secondary\">\n <ng-container *ngTemplateOutlet=\"propertyKey; context: { property }\" />\n <span>:</span>\n </div>\n\n @if (property.isCollapsible && property.showMaxLength) {\n <span class=\"is-flex is-size-7 | property-value\">\n {{ property.value | ellipsis: property.showMaxLength }}\n </span>\n\n <a class=\"is-flex is-size-7\" (click)=\"property.showMaxLength = 0\">Show more</a>\n } @else {\n <span class=\"is-flex is-size-7 | property-value\">\n {{ property.value }}\n </span>\n }\n\n @if (property.schema?.internal && property.key !== 'originalId') {\n <div class=\"property-internal\" ngbTooltip=\"This value is auto-generated.\">\n <he-svg-icon name=\"autogenerate-circle\" />\n </div>\n }\n\n <div class=\"copy-button\">\n <he-clipboard [value]=\"property.value\" [hideText]=\"true\" />\n </div>\n </div>\n\n <div class=\"is-flex is-gap-8\">\n @if (property.externalUrl?.url) {\n <a\n [href]=\"property.externalUrl.url + (property.externalUrl.urlParamValue ? property.value : '')\"\n target=\"_blank\"\n [title]=\"property.externalUrl.title\"\n class=\"ml-2\"\n [ngClass]=\"{ 'is-info': property.key === 'type' }\"\n [attr.disabled]=\"property.externalUrl.urlParamValue && !property.value ? true : null\">\n <he-svg-icon [name]=\"property.externalUrl.icon || 'external-link'\" />\n </a>\n }\n </div>\n</ng-template>\n\n<ng-template #showProperty let-property>\n @if ((isOpen() || property.closedVisible) && !property.isHidden) {\n @if (property.properties.length) {\n <div class=\"is-flex is-flex-direction-column is-flex-wrap-wrap w-100 | properties-container\">\n <div\n class=\"is-flex is-flex-direction-row is-justify-content-space-between is-gap-4 has-text-secondary w-100 | properties-container--title\"\n (click)=\"property.isOpen = !property.isOpen\"\n pointer\n [class.is-open]=\"property.isOpen\"\n [class.is-group-error-danger]=\"property.hasError\"\n [class.is-group-error-warning]=\"!property.hasError && property.hasWarning\">\n <div class=\"is-flex is-gap-8 has-text-weight-bold\">\n <!-- @if (property.schemaType === SchemaType.Term) {\n <he-node-icon [type]=\"property.schemaType\" />\n } -->\n <ng-container *ngTemplateOutlet=\"propertyKey; context: { property }\" />\n </div>\n <he-svg-icon [name]=\"property.isOpen ? 'minus' : 'plus'\" />\n </div>\n @if (property.isOpen) {\n @if (property.error) {\n <ng-container *ngTemplateOutlet=\"propertyError; context: { property, classes: 'is-my-1 is-px-3' }\" />\n }\n @if (property.isArray) {\n <div class=\"py-2 px-3 w-100\">\n @if (property.hasError) {\n <div class=\"is-flex is-align-items-center is-size-7 is-italic is-mb-2 has-text-warning\">\n <he-svg-icon name=\"exclamation-triangle\" />\n @if (showAllErrors()) {\n <a class=\"is-pl-2\" (click)=\"showAllErrors.set(false)\">Only show items in error.</a>\n } @else {\n <span class=\"is-pl-2\">Only showing items in error.</span>\n <a class=\"is-pl-1\" (click)=\"showAllErrors.set(true)\">Show all items</a>\n }\n </div>\n }\n\n @for (prop2 of property.properties; track trackByProperty($index, prop2)) {\n @if (prop2.key && (showAllErrors() || !property.hasError || prop2.hasError)) {\n <div class=\"pt-2 my-4 | property-array-container\" [id]=\"prop2.fullKey + '_' + prop2.id\">\n <div class=\"ml-2 is-mb-2 | property-array-number\">\n <span class=\"has-text-info is-px-1 | number-tag\">{{ prop2.key }}</span>\n </div>\n @if (prop2.error) {\n <ng-container\n *ngTemplateOutlet=\"propertyError; context: { property: prop2, classes: 'is-py-1 is-px-3' }\" />\n }\n <div class=\"p-3\">\n <div class=\"is-flex is-flex-direction-row is-flex-wrap-wrap is-align-self-stretch\">\n @for (prop3 of prop2.properties; track trackByProperty($index, prop3)) {\n <ng-container *ngTemplateOutlet=\"showProperty; context: { $implicit: prop3 }\" />\n }\n </div>\n </div>\n </div>\n }\n }\n </div>\n } @else {\n <div class=\"is-flex is-flex-direction-row is-flex-wrap-wrap is-align-self-stretch\">\n @for (prop2 of property.properties; track trackByProperty($index, prop2)) {\n <ng-container *ngTemplateOutlet=\"showProperty; context: { $implicit: prop2 }\" />\n }\n </div>\n }\n }\n </div>\n } @else {\n <div\n class=\"is-flex is-flex-direction-column is-gap-4 | property-container\"\n [class.is-error-danger]=\"property.hasError\"\n [class.is-error-warning]=\"!property.hasError && property.hasWarning\">\n <div\n class=\"is-flex is-flex-direction-row is-align-items-center is-justify-content-space-between is-gap-4 | field-container\">\n @if (property.key) {\n <ng-container *ngTemplateOutlet=\"propertyContent; context: { property }\" />\n }\n </div>\n @if (property.hasError || property.hasWarning) {\n <ng-container *ngTemplateOutlet=\"propertyError; context: { property }\" />\n }\n </div>\n }\n }\n</ng-template>\n\n<ng-template #propertyError let-property=\"property\" let-classes=\"classes\">\n @if (property.error?.message) {\n <div\n class=\"is-flex is-flex-direction-row is-gap-8 is-size-6 is-m-0 w-100 has-text-grey {{ classes }} | property-error\"\n [class.is-error-danger]=\"property.hasError\"\n [class.is-error-warning]=\"!property.hasError && property.hasWarning\">\n @if (property.hasError) {\n <he-svg-icon name=\"xmark-circle\" size=\"24\" class=\"has-text-danger is-flex-grow-0\" />\n } @else {\n <he-svg-icon name=\"exclamation-triangle\" size=\"24\" class=\"has-text-warning is-flex-grow-0\" />\n }\n\n <div class=\"is-flex is-flex-grow-1\">\n <span [innerHTML]=\"property.error.message\"></span>\n </div>\n\n @if (errorGuidePrefix() && property.errorGuidePageId) {\n <he-guide-overlay class=\"is-flex-grow-0\" [pageId]=\"errorGuidePrefix() + property.errorGuidePageId\" />\n }\n\n @if (property.error.index >= 0) {\n @if (property.hasWarning) {\n <a (click)=\"resolveError(property)\">\n <he-svg-icon name=\"checkmark\" />\n <span class=\"is-pl-2\">Resolved</span>\n </a>\n }\n }\n </div>\n }\n</ng-template>\n\n<ng-template #propertyMap let-property>\n @if (showMap()) {\n <div class=\"is-flex is-flex-direction-column is-flex-wrap-wrap w-100 | properties-container\">\n <div\n class=\"is-flex is-flex-direction-row is-justify-content-space-between is-gap-4 w-100 | properties-container--title\"\n (click)=\"mapVisible.set(!mapVisible())\"\n pointer\n [class.is-open]=\"mapVisible()\">\n <span class=\"is-size-6\">View on Map</span>\n <he-svg-icon [name]=\"mapVisible() ? 'minus' : 'plus'\" />\n </div>\n @if (mapVisible()) {\n <he-sites-maps [sites]=\"[node()]\" [showNotice]=\"false\" />\n }\n </div>\n }\n</ng-template>\n\n<ng-template #nodeLink let-url=\"url\" let-nodeType=\"nodeType\">\n <a class=\"external-link\" [href]=\"url\" target=\"_blank\" (click)=\"$event.stopPropagation()\">\n <span class=\"is-hidden-mobile\">View</span>\n <span class=\"is-hidden-mobile is-pl-1\">{{ nodeType }}</span>\n <he-svg-icon name=\"external-link\" class=\"ml-2\" />\n </a>\n</ng-template>\n", styles: [".notification{color:#0a0a0a!important}.notification.is-success{background-color:#d5f3d8}.notification.is-success *:not(.button):not(.icon):not(.dropdown-item)>fa-icon,.notification.is-success>fa-icon,.notification.is-success *:not(.button):not(.icon):not(.dropdown-item)>he-svg-icon,.notification.is-success>he-svg-icon{color:#2b8434}.notification.is-info{background-color:#d3ebed}.notification.is-info *:not(.button):not(.icon):not(.dropdown-item)>fa-icon,.notification.is-info>fa-icon,.notification.is-info *:not(.button):not(.icon):not(.dropdown-item)>he-svg-icon,.notification.is-info>he-svg-icon{color:#249da5}.notification.is-warning{background-color:#ffdec0}.notification.is-warning *:not(.button):not(.icon):not(.dropdown-item)>fa-icon,.notification.is-warning>fa-icon,.notification.is-warning *:not(.button):not(.icon):not(.dropdown-item)>he-svg-icon,.notification.is-warning>he-svg-icon{color:#ff881b}.notification.is-danger{background-color:#ffcdd0}.notification.is-danger *:not(.button):not(.icon):not(.dropdown-item)>fa-icon,.notification.is-danger>fa-icon,.notification.is-danger *:not(.button):not(.icon):not(.dropdown-item)>he-svg-icon,.notification.is-danger>he-svg-icon{color:#ff3844}he-sites-maps{height:200px}.external-link{color:#4c7194}.files-form-container{box-shadow:2px 2px 4px #00000029;background-color:#fff}.files-form-header{padding:10px 12px;background-color:#dbe3ea;border-top-left-radius:3px;border-top-right-radius:3px}.properties-container--title,.property-container{padding:6px 12px}.property-container{flex:none;flex-grow:1;min-width:50%;max-width:100%;border:1px solid #f5f5f5}.property-container.is-error-danger{border-color:#ff3844}.property-container.is-error-warning{border-color:#ff881b}.properties-container--title{border-top:1px solid #dbe3ea;border-bottom:1px solid #dbe3ea;background:#f5f7f9}.properties-container .properties-container{border-radius:6px;border:1px solid #dbe3ea;margin:4px 0}.properties-container .properties-container--title{border-top:none}.property-internal{color:#249da5}.property-key,.property-value{word-break:break-word}.property-array-container{border-radius:6px;border:1px solid #dbdbdb}.number-tag{border:1px solid #249da5;border-radius:50%}.copy-button{visibility:hidden}.field-container:hover .copy-button{visibility:visible}.is-group-error-danger{background-color:#ffcdd0}.is-group-error-warning{background-color:#ffdec0}.property-error.is-error-danger{background-color:#ffeced}.field-container+.property-error.is-error-danger{background-color:transparent;color:#ff3844!important}.property-error.is-error-warning{background-color:#fff5ec}.field-container+.property-error.is-error-warning{background-color:transparent;color:#ff881b!important}::ng-deep .property-tooltip{background-color:#fff;color:#0a0a0a;border:1px solid #dbdbdb;z-index:11;max-width:50vw;max-height:50vh;overflow:auto}::ng-deep .property-tooltip pre{white-space:pre-wrap}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "directive", type: NgbTooltip, selector: "[ngbTooltip]", inputs: ["animation", "autoClose", "placement", "popperOptions", "triggers", "positionTarget", "container", "disableTooltip", "tooltipClass", "tooltipContext", "openDelay", "closeDelay", "ngbTooltip"], outputs: ["shown", "hidden"], exportAs: ["ngbTooltip"] }, { kind: "component", type: NodeIconComponent, selector: "he-node-icon", inputs: ["type", "size"] }, { kind: "component", type: SitesMapsComponent, selector: "he-sites-maps", inputs: ["loadPolygons", "sites", "zoom", "showNotice"] }, { kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "component", type: ClipboardComponent, selector: "he-clipboard", inputs: ["icon", "value", "disabled", "hideText", "hideIcon", "size", "clipboardClass", "tooltipPlacement"] }, { kind: "component", type: GuideOverlayComponent, selector: "he-guide-overlay", inputs: ["pageId", "width", "height", "positions"], outputs: ["widthChange", "heightChange"] }, { kind: "pipe", type: EllipsisPipe, name: "ellipsis" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
14926
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: FilesFormComponent, isStandalone: true, selector: "he-files-form", inputs: { isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, showNodeLink: { classPropertyName: "showNodeLink", publicName: "showNodeLink", isSignal: true, isRequired: false, transformFunction: null }, errorGuidePrefix: { classPropertyName: "errorGuidePrefix", publicName: "errorGuidePrefix", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpen: "isOpenChange", nodeErorrResolved: "nodeErorrResolved" }, exportAs: ["filesForm"], ngImport: i0, template: "<div class=\"is-flex is-flex-direction-column is-align-items-flex-start is-align-self-stretch | files-form-container\">\n <div\n class=\"is-flex is-flex-direction-row is-justify-content-space-between is-align-items-center is-align-self-stretch is-gap-16 px-4 py-2 has-text-secondary | files-form-header\"\n (click)=\"isOpen.set(!isOpen())\"\n pointer>\n <div class=\"is-flex is-gap-8 is-align-items-center\">\n @if (nodeProperty()) {\n <he-node-icon class=\"is-flex\" [type]=\"schemaType()\" [size]=\"24\" />\n <span class=\"has-text-weight-bold has-text-secondary is-size-5\">{{ schemaType() }}</span>\n @if (nodeProperty().hasError || hasError()) {\n <he-svg-icon name=\"xmark-circle\" class=\"has-text-danger is-flex is-align-items-center\" />\n <span class=\"has-text-danger has-text-weight-bold is-italic\">Error</span>\n } @else if (nodeProperty().hasWarning || hasWarning()) {\n <he-svg-icon name=\"exclamation-triangle\" class=\"has-text-warning is-flex is-align-items-center\" />\n <span class=\"has-text-warning has-text-weight-bold is-italic\">Warning</span>\n }\n }\n </div>\n @if ((showNodeLink() && nodeUrl()) || canOpen()) {\n <div class=\"is-flex is-flex-direction-row is-align-items-center is-gap-16\">\n @if (showNodeLink() && nodeUrl()) {\n <ng-container *ngTemplateOutlet=\"nodeLink; context: { url: nodeUrl(), nodeType: schemaType() }\" />\n }\n @if (canOpen()) {\n <he-svg-icon [name]=\"isOpen() ? 'minus' : 'plus'\" />\n }\n </div>\n }\n </div>\n\n <div class=\"is-flex is-flex-direction-column is-align-items-flex-start is-align-self-stretch\">\n @if (isOpen()) {\n @if (nodeProperty()?.error) {\n <ng-container\n *ngTemplateOutlet=\"\n propertyError;\n context: { property: nodeProperty(), classes: 'is-my-1 is-py-1 is-px-3' }\n \" />\n }\n }\n\n @if (unmatchedErrors().length) {\n @for (property of unmatchedErrors(); track property.id) {\n <ng-container *ngTemplateOutlet=\"propertyError; context: { property, classes: 'is-my-1 is-py-1 is-px-3' }\" />\n }\n }\n\n <div class=\"is-flex is-flex-direction-row is-flex-wrap-wrap is-align-self-stretch\">\n @for (property of properties(); track trackByProperty($index, property)) {\n <ng-container *ngTemplateOutlet=\"showProperty; context: { $implicit: property }\" />\n }\n </div>\n\n @if (isOpen()) {\n <ng-container *ngTemplateOutlet=\"propertyMap; context: { $implicit: nodeProperty() }\" />\n }\n </div>\n\n <ng-content />\n</div>\n\n<ng-template #propertyKeyTooltip let-property=\"property\">\n <markdown [data]=\"property.schema.description\" />\n</ng-template>\n\n<ng-template #propertyKey let-property=\"property\">\n @if (property.schema?.description) {\n <span\n class=\"trigger-popover | property-key\"\n [ngbTooltip]=\"propertyKeyTooltip\"\n [tooltipContext]=\"{ property }\"\n triggers=\"click\"\n autoClose=\"outside\"\n tooltipClass=\"property-tooltip\"\n placement=\"bottom-left auto\"\n container=\"body\"\n (click)=\"$event.stopPropagation()\">\n <span>{{ property.key }}</span>\n </span>\n } @else {\n <span>{{ property.key }}</span>\n }\n</ng-template>\n\n<ng-template #propertyContent let-property=\"property\">\n <div class=\"is-flex is-align-items-center is-flex-wrap-wrap is-gap-4\">\n <div class=\"is-flex has-text-secondary\">\n <ng-container *ngTemplateOutlet=\"propertyKey; context: { property }\" />\n <span>:</span>\n </div>\n\n @if (property.isCollapsible && property.showMaxLength) {\n <span class=\"is-flex is-size-7 | property-value\">\n {{ property.value | ellipsis: property.showMaxLength }}\n </span>\n\n <a class=\"is-flex is-size-7\" (click)=\"property.showMaxLength = 0\">Show more</a>\n } @else {\n <span class=\"is-flex is-size-7 | property-value\">\n {{ property.value }}\n </span>\n }\n\n @if (property.schema?.internal && property.key !== 'originalId') {\n <div class=\"property-internal\" ngbTooltip=\"This value is auto-generated.\">\n <he-svg-icon name=\"autogenerate-circle\" />\n </div>\n }\n\n <div class=\"copy-button\">\n <he-clipboard [value]=\"property.value\" [hideText]=\"true\" />\n </div>\n </div>\n\n <div class=\"is-flex is-gap-8\">\n @if (property.externalUrl?.url) {\n <a\n [href]=\"property.externalUrl.url + (property.externalUrl.urlParamValue ? property.value : '')\"\n target=\"_blank\"\n [title]=\"property.externalUrl.title\"\n class=\"ml-2\"\n [ngClass]=\"{ 'is-info': property.key === 'type' }\"\n [attr.disabled]=\"property.externalUrl.urlParamValue && !property.value ? true : null\">\n <he-svg-icon [name]=\"property.externalUrl.icon || 'external-link'\" />\n </a>\n }\n </div>\n</ng-template>\n\n<ng-template #showProperty let-property>\n @if ((isOpen() || property.closedVisible) && !property.isHidden) {\n @if (property.properties.length) {\n <div class=\"is-flex is-flex-direction-column is-flex-wrap-wrap w-100 | properties-container\">\n <div\n class=\"is-flex is-flex-direction-row is-justify-content-space-between is-gap-4 has-text-secondary w-100 | properties-container--title\"\n (click)=\"property.isOpen = !property.isOpen\"\n pointer\n [class.is-open]=\"property.isOpen\"\n [class.is-group-error-danger]=\"property.hasError\"\n [class.is-group-error-warning]=\"!property.hasError && property.hasWarning\">\n <div class=\"is-flex is-gap-8 has-text-weight-bold\">\n <!-- @if (property.schemaType === SchemaType.Term) {\n <he-node-icon [type]=\"property.schemaType\" />\n } -->\n <ng-container *ngTemplateOutlet=\"propertyKey; context: { property }\" />\n </div>\n <he-svg-icon [name]=\"property.isOpen ? 'minus' : 'plus'\" />\n </div>\n @if (property.isOpen) {\n @if (property.error) {\n <ng-container *ngTemplateOutlet=\"propertyError; context: { property, classes: 'is-my-1 is-px-3' }\" />\n }\n @if (property.isArray) {\n <div class=\"py-2 px-3 w-100\">\n @if (property.hasError) {\n <div class=\"is-flex is-align-items-center is-size-7 is-italic is-mb-2 has-text-warning\">\n <he-svg-icon name=\"exclamation-triangle\" />\n @if (showAllErrors()) {\n <a class=\"is-pl-2\" (click)=\"showAllErrors.set(false)\">Only show items in error.</a>\n } @else {\n <span class=\"is-pl-2\">Only showing items in error.</span>\n <a class=\"is-pl-1\" (click)=\"showAllErrors.set(true)\">Show all items</a>\n }\n </div>\n }\n\n @for (prop2 of property.properties; track trackByProperty($index, prop2)) {\n @if (prop2.key && (showAllErrors() || !property.hasError || prop2.hasError)) {\n <div class=\"pt-2 my-4 | property-array-container\" [id]=\"prop2.fullKey + '_' + prop2.id\">\n <div class=\"ml-2 is-mb-2 | property-array-number\">\n <span class=\"has-text-info is-px-1 | number-tag\">{{ prop2.key }}</span>\n </div>\n @if (prop2.error) {\n <ng-container\n *ngTemplateOutlet=\"propertyError; context: { property: prop2, classes: 'is-py-1 is-px-3' }\" />\n }\n <div class=\"p-3\">\n <div class=\"is-flex is-flex-direction-row is-flex-wrap-wrap is-align-self-stretch\">\n @for (prop3 of prop2.properties; track trackByProperty($index, prop3)) {\n <ng-container *ngTemplateOutlet=\"showProperty; context: { $implicit: prop3 }\" />\n }\n </div>\n </div>\n </div>\n }\n }\n </div>\n } @else {\n <div class=\"is-flex is-flex-direction-row is-flex-wrap-wrap is-align-self-stretch\">\n @for (prop2 of property.properties; track trackByProperty($index, prop2)) {\n <ng-container *ngTemplateOutlet=\"showProperty; context: { $implicit: prop2 }\" />\n }\n </div>\n }\n }\n </div>\n } @else {\n <div\n class=\"is-flex is-flex-direction-column is-gap-4 | property-container\"\n [class.is-error-danger]=\"property.hasError\"\n [class.is-error-warning]=\"!property.hasError && property.hasWarning\">\n <div\n class=\"is-flex is-flex-direction-row is-align-items-center is-justify-content-space-between is-gap-4 | field-container\">\n @if (property.key) {\n <ng-container *ngTemplateOutlet=\"propertyContent; context: { property }\" />\n }\n </div>\n @if (property.hasError || property.hasWarning) {\n <ng-container *ngTemplateOutlet=\"propertyError; context: { property }\" />\n }\n </div>\n }\n }\n</ng-template>\n\n<ng-template #propertyError let-property=\"property\" let-classes=\"classes\">\n @if (property.error?.message) {\n <div\n class=\"is-flex is-flex-direction-row is-gap-8 is-size-6 is-m-0 w-100 has-text-grey {{ classes }} | property-error\"\n [class.is-error-danger]=\"property.hasError\"\n [class.is-error-warning]=\"!property.hasError && property.hasWarning\">\n @if (property.hasError) {\n <he-svg-icon name=\"xmark-circle\" size=\"24\" class=\"has-text-danger is-flex-grow-0\" />\n } @else {\n <he-svg-icon name=\"exclamation-triangle\" size=\"24\" class=\"has-text-warning is-flex-grow-0\" />\n }\n\n <div class=\"is-flex is-flex-direction-column is-align-items-start is-flex-grow-1\">\n <span [innerHTML]=\"property.error.message\"></span>\n <he-files-error-distribution [error]=\"property.error\" />\n </div>\n\n @if (errorGuidePrefix() && property.errorGuidePageId) {\n <he-guide-overlay class=\"is-flex-grow-0\" [pageId]=\"errorGuidePrefix() + property.errorGuidePageId\" />\n }\n\n @if (property.error.index >= 0) {\n @if (property.hasWarning) {\n <a (click)=\"resolveError(property)\">\n <he-svg-icon name=\"checkmark\" />\n <span class=\"is-pl-2\">Resolved</span>\n </a>\n }\n }\n </div>\n }\n</ng-template>\n\n<ng-template #propertyMap let-property>\n @if (showMap()) {\n <div class=\"is-flex is-flex-direction-column is-flex-wrap-wrap w-100 | properties-container\">\n <div\n class=\"is-flex is-flex-direction-row is-justify-content-space-between is-gap-4 w-100 | properties-container--title\"\n (click)=\"mapVisible.set(!mapVisible())\"\n pointer\n [class.is-open]=\"mapVisible()\">\n <span class=\"is-size-6\">View on Map</span>\n <he-svg-icon [name]=\"mapVisible() ? 'minus' : 'plus'\" />\n </div>\n @if (mapVisible()) {\n <he-sites-maps [sites]=\"[node()]\" [showNotice]=\"false\" />\n }\n </div>\n }\n</ng-template>\n\n<ng-template #nodeLink let-url=\"url\" let-nodeType=\"nodeType\">\n <a class=\"external-link\" [href]=\"url\" target=\"_blank\" (click)=\"$event.stopPropagation()\">\n <span class=\"is-hidden-mobile\">View</span>\n <span class=\"is-hidden-mobile is-pl-1\">{{ nodeType }}</span>\n <he-svg-icon name=\"external-link\" class=\"ml-2\" />\n </a>\n</ng-template>\n", styles: [".notification{color:#0a0a0a!important}.notification.is-success{background-color:#d5f3d8}.notification.is-success *:not(.button):not(.icon):not(.dropdown-item)>fa-icon,.notification.is-success>fa-icon,.notification.is-success *:not(.button):not(.icon):not(.dropdown-item)>he-svg-icon,.notification.is-success>he-svg-icon{color:#2b8434}.notification.is-info{background-color:#d3ebed}.notification.is-info *:not(.button):not(.icon):not(.dropdown-item)>fa-icon,.notification.is-info>fa-icon,.notification.is-info *:not(.button):not(.icon):not(.dropdown-item)>he-svg-icon,.notification.is-info>he-svg-icon{color:#249da5}.notification.is-warning{background-color:#ffdec0}.notification.is-warning *:not(.button):not(.icon):not(.dropdown-item)>fa-icon,.notification.is-warning>fa-icon,.notification.is-warning *:not(.button):not(.icon):not(.dropdown-item)>he-svg-icon,.notification.is-warning>he-svg-icon{color:#ff881b}.notification.is-danger{background-color:#ffcdd0}.notification.is-danger *:not(.button):not(.icon):not(.dropdown-item)>fa-icon,.notification.is-danger>fa-icon,.notification.is-danger *:not(.button):not(.icon):not(.dropdown-item)>he-svg-icon,.notification.is-danger>he-svg-icon{color:#ff3844}he-sites-maps{height:200px}.external-link{color:#4c7194}.files-form-container{box-shadow:2px 2px 4px #00000029;background-color:#fff}.files-form-header{padding:10px 12px;background-color:#dbe3ea;border-top-left-radius:3px;border-top-right-radius:3px}.properties-container--title,.property-container{padding:6px 12px}.property-container{flex:none;flex-grow:1;min-width:50%;max-width:100%;border:1px solid #f5f5f5}.property-container.is-error-danger{border-color:#ff3844}.property-container.is-error-warning{border-color:#ff881b}.properties-container--title{border-top:1px solid #dbe3ea;border-bottom:1px solid #dbe3ea;background:#f5f7f9}.properties-container .properties-container{border-radius:6px;border:1px solid #dbe3ea;margin:4px 0}.properties-container .properties-container--title{border-top:none}.property-internal{color:#249da5}.property-key,.property-value{word-break:break-word}.property-array-container{border-radius:6px;border:1px solid #dbdbdb}.number-tag{border:1px solid #249da5;border-radius:50%}.copy-button{visibility:hidden}.field-container:hover .copy-button{visibility:visible}.is-group-error-danger{background-color:#ffcdd0}.is-group-error-warning{background-color:#ffdec0}.property-error.is-error-danger{background-color:#ffeced}.field-container+.property-error.is-error-danger{background-color:transparent;color:#ff3844!important}.property-error.is-error-warning{background-color:#fff5ec}.field-container+.property-error.is-error-warning{background-color:transparent;color:#ff881b!important}::ng-deep .property-tooltip{background-color:#fff;color:#0a0a0a;border:1px solid #dbdbdb;z-index:11;max-width:50vw;max-height:50vh;overflow:auto}::ng-deep .property-tooltip pre{white-space:pre-wrap}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "directive", type: NgbTooltip, selector: "[ngbTooltip]", inputs: ["animation", "autoClose", "placement", "popperOptions", "triggers", "positionTarget", "container", "disableTooltip", "tooltipClass", "tooltipContext", "openDelay", "closeDelay", "ngbTooltip"], outputs: ["shown", "hidden"], exportAs: ["ngbTooltip"] }, { kind: "component", type: NodeIconComponent, selector: "he-node-icon", inputs: ["type", "size"] }, { kind: "component", type: SitesMapsComponent, selector: "he-sites-maps", inputs: ["loadPolygons", "sites", "zoom", "showNotice"] }, { kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "component", type: ClipboardComponent, selector: "he-clipboard", inputs: ["icon", "value", "disabled", "hideText", "hideIcon", "size", "clipboardClass", "tooltipPlacement"] }, { kind: "component", type: GuideOverlayComponent, selector: "he-guide-overlay", inputs: ["pageId", "width", "height", "positions"], outputs: ["widthChange", "heightChange"] }, { kind: "component", type: FilesErrorDistributionComponent, selector: "he-files-error-distribution", inputs: ["error"] }, { kind: "pipe", type: EllipsisPipe, name: "ellipsis" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
14335
14927
  }
14336
14928
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: FilesFormComponent, decorators: [{
14337
14929
  type: Component$1,
@@ -14346,8 +14938,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
14346
14938
  HESvgIconComponent,
14347
14939
  ClipboardComponent,
14348
14940
  GuideOverlayComponent,
14941
+ FilesErrorDistributionComponent,
14349
14942
  EllipsisPipe
14350
- ], template: "<div class=\"is-flex is-flex-direction-column is-align-items-flex-start is-align-self-stretch | files-form-container\">\n <div\n class=\"is-flex is-flex-direction-row is-justify-content-space-between is-align-items-center is-align-self-stretch is-gap-16 px-4 py-2 has-text-secondary | files-form-header\"\n (click)=\"isOpen.set(!isOpen())\"\n pointer>\n <div class=\"is-flex is-gap-8 is-align-items-center\">\n @if (nodeProperty()) {\n <he-node-icon class=\"is-flex\" [type]=\"schemaType()\" [size]=\"24\" />\n <span class=\"has-text-weight-bold has-text-secondary is-size-5\">{{ schemaType() }}</span>\n @if (nodeProperty().hasError || hasError()) {\n <he-svg-icon name=\"xmark-circle\" class=\"has-text-danger is-flex is-align-items-center\" />\n <span class=\"has-text-danger has-text-weight-bold is-italic\">Error</span>\n } @else if (nodeProperty().hasWarning || hasWarning()) {\n <he-svg-icon name=\"exclamation-triangle\" class=\"has-text-warning is-flex is-align-items-center\" />\n <span class=\"has-text-warning has-text-weight-bold is-italic\">Warning</span>\n }\n }\n </div>\n @if ((showNodeLink() && nodeUrl()) || canOpen()) {\n <div class=\"is-flex is-flex-direction-row is-align-items-center is-gap-16\">\n @if (showNodeLink() && nodeUrl()) {\n <ng-container *ngTemplateOutlet=\"nodeLink; context: { url: nodeUrl(), nodeType: schemaType() }\" />\n }\n @if (canOpen()) {\n <he-svg-icon [name]=\"isOpen() ? 'minus' : 'plus'\" />\n }\n </div>\n }\n </div>\n\n <div class=\"is-flex is-flex-direction-column is-align-items-flex-start is-align-self-stretch\">\n @if (isOpen()) {\n @if (nodeProperty()?.error) {\n <ng-container\n *ngTemplateOutlet=\"\n propertyError;\n context: { property: nodeProperty(), classes: 'is-my-1 is-py-1 is-px-3' }\n \" />\n }\n }\n\n @if (unmatchedErrors().length) {\n @for (property of unmatchedErrors(); track property.id) {\n <ng-container *ngTemplateOutlet=\"propertyError; context: { property, classes: 'is-my-1 is-py-1 is-px-3' }\" />\n }\n }\n\n <div class=\"is-flex is-flex-direction-row is-flex-wrap-wrap is-align-self-stretch\">\n @for (property of properties(); track trackByProperty($index, property)) {\n <ng-container *ngTemplateOutlet=\"showProperty; context: { $implicit: property }\" />\n }\n </div>\n\n @if (isOpen()) {\n <ng-container *ngTemplateOutlet=\"propertyMap; context: { $implicit: nodeProperty() }\" />\n }\n </div>\n\n <ng-content />\n</div>\n\n<ng-template #propertyKeyTooltip let-property=\"property\">\n <markdown [data]=\"property.schema.description\" />\n</ng-template>\n\n<ng-template #propertyKey let-property=\"property\">\n @if (property.schema?.description) {\n <span\n class=\"trigger-popover | property-key\"\n [ngbTooltip]=\"propertyKeyTooltip\"\n [tooltipContext]=\"{ property }\"\n triggers=\"click\"\n autoClose=\"outside\"\n tooltipClass=\"property-tooltip\"\n placement=\"bottom-left auto\"\n container=\"body\"\n (click)=\"$event.stopPropagation()\">\n <span>{{ property.key }}</span>\n </span>\n } @else {\n <span>{{ property.key }}</span>\n }\n</ng-template>\n\n<ng-template #propertyContent let-property=\"property\">\n <div class=\"is-flex is-align-items-center is-flex-wrap-wrap is-gap-4\">\n <div class=\"is-flex has-text-secondary\">\n <ng-container *ngTemplateOutlet=\"propertyKey; context: { property }\" />\n <span>:</span>\n </div>\n\n @if (property.isCollapsible && property.showMaxLength) {\n <span class=\"is-flex is-size-7 | property-value\">\n {{ property.value | ellipsis: property.showMaxLength }}\n </span>\n\n <a class=\"is-flex is-size-7\" (click)=\"property.showMaxLength = 0\">Show more</a>\n } @else {\n <span class=\"is-flex is-size-7 | property-value\">\n {{ property.value }}\n </span>\n }\n\n @if (property.schema?.internal && property.key !== 'originalId') {\n <div class=\"property-internal\" ngbTooltip=\"This value is auto-generated.\">\n <he-svg-icon name=\"autogenerate-circle\" />\n </div>\n }\n\n <div class=\"copy-button\">\n <he-clipboard [value]=\"property.value\" [hideText]=\"true\" />\n </div>\n </div>\n\n <div class=\"is-flex is-gap-8\">\n @if (property.externalUrl?.url) {\n <a\n [href]=\"property.externalUrl.url + (property.externalUrl.urlParamValue ? property.value : '')\"\n target=\"_blank\"\n [title]=\"property.externalUrl.title\"\n class=\"ml-2\"\n [ngClass]=\"{ 'is-info': property.key === 'type' }\"\n [attr.disabled]=\"property.externalUrl.urlParamValue && !property.value ? true : null\">\n <he-svg-icon [name]=\"property.externalUrl.icon || 'external-link'\" />\n </a>\n }\n </div>\n</ng-template>\n\n<ng-template #showProperty let-property>\n @if ((isOpen() || property.closedVisible) && !property.isHidden) {\n @if (property.properties.length) {\n <div class=\"is-flex is-flex-direction-column is-flex-wrap-wrap w-100 | properties-container\">\n <div\n class=\"is-flex is-flex-direction-row is-justify-content-space-between is-gap-4 has-text-secondary w-100 | properties-container--title\"\n (click)=\"property.isOpen = !property.isOpen\"\n pointer\n [class.is-open]=\"property.isOpen\"\n [class.is-group-error-danger]=\"property.hasError\"\n [class.is-group-error-warning]=\"!property.hasError && property.hasWarning\">\n <div class=\"is-flex is-gap-8 has-text-weight-bold\">\n <!-- @if (property.schemaType === SchemaType.Term) {\n <he-node-icon [type]=\"property.schemaType\" />\n } -->\n <ng-container *ngTemplateOutlet=\"propertyKey; context: { property }\" />\n </div>\n <he-svg-icon [name]=\"property.isOpen ? 'minus' : 'plus'\" />\n </div>\n @if (property.isOpen) {\n @if (property.error) {\n <ng-container *ngTemplateOutlet=\"propertyError; context: { property, classes: 'is-my-1 is-px-3' }\" />\n }\n @if (property.isArray) {\n <div class=\"py-2 px-3 w-100\">\n @if (property.hasError) {\n <div class=\"is-flex is-align-items-center is-size-7 is-italic is-mb-2 has-text-warning\">\n <he-svg-icon name=\"exclamation-triangle\" />\n @if (showAllErrors()) {\n <a class=\"is-pl-2\" (click)=\"showAllErrors.set(false)\">Only show items in error.</a>\n } @else {\n <span class=\"is-pl-2\">Only showing items in error.</span>\n <a class=\"is-pl-1\" (click)=\"showAllErrors.set(true)\">Show all items</a>\n }\n </div>\n }\n\n @for (prop2 of property.properties; track trackByProperty($index, prop2)) {\n @if (prop2.key && (showAllErrors() || !property.hasError || prop2.hasError)) {\n <div class=\"pt-2 my-4 | property-array-container\" [id]=\"prop2.fullKey + '_' + prop2.id\">\n <div class=\"ml-2 is-mb-2 | property-array-number\">\n <span class=\"has-text-info is-px-1 | number-tag\">{{ prop2.key }}</span>\n </div>\n @if (prop2.error) {\n <ng-container\n *ngTemplateOutlet=\"propertyError; context: { property: prop2, classes: 'is-py-1 is-px-3' }\" />\n }\n <div class=\"p-3\">\n <div class=\"is-flex is-flex-direction-row is-flex-wrap-wrap is-align-self-stretch\">\n @for (prop3 of prop2.properties; track trackByProperty($index, prop3)) {\n <ng-container *ngTemplateOutlet=\"showProperty; context: { $implicit: prop3 }\" />\n }\n </div>\n </div>\n </div>\n }\n }\n </div>\n } @else {\n <div class=\"is-flex is-flex-direction-row is-flex-wrap-wrap is-align-self-stretch\">\n @for (prop2 of property.properties; track trackByProperty($index, prop2)) {\n <ng-container *ngTemplateOutlet=\"showProperty; context: { $implicit: prop2 }\" />\n }\n </div>\n }\n }\n </div>\n } @else {\n <div\n class=\"is-flex is-flex-direction-column is-gap-4 | property-container\"\n [class.is-error-danger]=\"property.hasError\"\n [class.is-error-warning]=\"!property.hasError && property.hasWarning\">\n <div\n class=\"is-flex is-flex-direction-row is-align-items-center is-justify-content-space-between is-gap-4 | field-container\">\n @if (property.key) {\n <ng-container *ngTemplateOutlet=\"propertyContent; context: { property }\" />\n }\n </div>\n @if (property.hasError || property.hasWarning) {\n <ng-container *ngTemplateOutlet=\"propertyError; context: { property }\" />\n }\n </div>\n }\n }\n</ng-template>\n\n<ng-template #propertyError let-property=\"property\" let-classes=\"classes\">\n @if (property.error?.message) {\n <div\n class=\"is-flex is-flex-direction-row is-gap-8 is-size-6 is-m-0 w-100 has-text-grey {{ classes }} | property-error\"\n [class.is-error-danger]=\"property.hasError\"\n [class.is-error-warning]=\"!property.hasError && property.hasWarning\">\n @if (property.hasError) {\n <he-svg-icon name=\"xmark-circle\" size=\"24\" class=\"has-text-danger is-flex-grow-0\" />\n } @else {\n <he-svg-icon name=\"exclamation-triangle\" size=\"24\" class=\"has-text-warning is-flex-grow-0\" />\n }\n\n <div class=\"is-flex is-flex-grow-1\">\n <span [innerHTML]=\"property.error.message\"></span>\n </div>\n\n @if (errorGuidePrefix() && property.errorGuidePageId) {\n <he-guide-overlay class=\"is-flex-grow-0\" [pageId]=\"errorGuidePrefix() + property.errorGuidePageId\" />\n }\n\n @if (property.error.index >= 0) {\n @if (property.hasWarning) {\n <a (click)=\"resolveError(property)\">\n <he-svg-icon name=\"checkmark\" />\n <span class=\"is-pl-2\">Resolved</span>\n </a>\n }\n }\n </div>\n }\n</ng-template>\n\n<ng-template #propertyMap let-property>\n @if (showMap()) {\n <div class=\"is-flex is-flex-direction-column is-flex-wrap-wrap w-100 | properties-container\">\n <div\n class=\"is-flex is-flex-direction-row is-justify-content-space-between is-gap-4 w-100 | properties-container--title\"\n (click)=\"mapVisible.set(!mapVisible())\"\n pointer\n [class.is-open]=\"mapVisible()\">\n <span class=\"is-size-6\">View on Map</span>\n <he-svg-icon [name]=\"mapVisible() ? 'minus' : 'plus'\" />\n </div>\n @if (mapVisible()) {\n <he-sites-maps [sites]=\"[node()]\" [showNotice]=\"false\" />\n }\n </div>\n }\n</ng-template>\n\n<ng-template #nodeLink let-url=\"url\" let-nodeType=\"nodeType\">\n <a class=\"external-link\" [href]=\"url\" target=\"_blank\" (click)=\"$event.stopPropagation()\">\n <span class=\"is-hidden-mobile\">View</span>\n <span class=\"is-hidden-mobile is-pl-1\">{{ nodeType }}</span>\n <he-svg-icon name=\"external-link\" class=\"ml-2\" />\n </a>\n</ng-template>\n", styles: [".notification{color:#0a0a0a!important}.notification.is-success{background-color:#d5f3d8}.notification.is-success *:not(.button):not(.icon):not(.dropdown-item)>fa-icon,.notification.is-success>fa-icon,.notification.is-success *:not(.button):not(.icon):not(.dropdown-item)>he-svg-icon,.notification.is-success>he-svg-icon{color:#2b8434}.notification.is-info{background-color:#d3ebed}.notification.is-info *:not(.button):not(.icon):not(.dropdown-item)>fa-icon,.notification.is-info>fa-icon,.notification.is-info *:not(.button):not(.icon):not(.dropdown-item)>he-svg-icon,.notification.is-info>he-svg-icon{color:#249da5}.notification.is-warning{background-color:#ffdec0}.notification.is-warning *:not(.button):not(.icon):not(.dropdown-item)>fa-icon,.notification.is-warning>fa-icon,.notification.is-warning *:not(.button):not(.icon):not(.dropdown-item)>he-svg-icon,.notification.is-warning>he-svg-icon{color:#ff881b}.notification.is-danger{background-color:#ffcdd0}.notification.is-danger *:not(.button):not(.icon):not(.dropdown-item)>fa-icon,.notification.is-danger>fa-icon,.notification.is-danger *:not(.button):not(.icon):not(.dropdown-item)>he-svg-icon,.notification.is-danger>he-svg-icon{color:#ff3844}he-sites-maps{height:200px}.external-link{color:#4c7194}.files-form-container{box-shadow:2px 2px 4px #00000029;background-color:#fff}.files-form-header{padding:10px 12px;background-color:#dbe3ea;border-top-left-radius:3px;border-top-right-radius:3px}.properties-container--title,.property-container{padding:6px 12px}.property-container{flex:none;flex-grow:1;min-width:50%;max-width:100%;border:1px solid #f5f5f5}.property-container.is-error-danger{border-color:#ff3844}.property-container.is-error-warning{border-color:#ff881b}.properties-container--title{border-top:1px solid #dbe3ea;border-bottom:1px solid #dbe3ea;background:#f5f7f9}.properties-container .properties-container{border-radius:6px;border:1px solid #dbe3ea;margin:4px 0}.properties-container .properties-container--title{border-top:none}.property-internal{color:#249da5}.property-key,.property-value{word-break:break-word}.property-array-container{border-radius:6px;border:1px solid #dbdbdb}.number-tag{border:1px solid #249da5;border-radius:50%}.copy-button{visibility:hidden}.field-container:hover .copy-button{visibility:visible}.is-group-error-danger{background-color:#ffcdd0}.is-group-error-warning{background-color:#ffdec0}.property-error.is-error-danger{background-color:#ffeced}.field-container+.property-error.is-error-danger{background-color:transparent;color:#ff3844!important}.property-error.is-error-warning{background-color:#fff5ec}.field-container+.property-error.is-error-warning{background-color:transparent;color:#ff881b!important}::ng-deep .property-tooltip{background-color:#fff;color:#0a0a0a;border:1px solid #dbdbdb;z-index:11;max-width:50vw;max-height:50vh;overflow:auto}::ng-deep .property-tooltip pre{white-space:pre-wrap}\n"] }]
14943
+ ], template: "<div class=\"is-flex is-flex-direction-column is-align-items-flex-start is-align-self-stretch | files-form-container\">\n <div\n class=\"is-flex is-flex-direction-row is-justify-content-space-between is-align-items-center is-align-self-stretch is-gap-16 px-4 py-2 has-text-secondary | files-form-header\"\n (click)=\"isOpen.set(!isOpen())\"\n pointer>\n <div class=\"is-flex is-gap-8 is-align-items-center\">\n @if (nodeProperty()) {\n <he-node-icon class=\"is-flex\" [type]=\"schemaType()\" [size]=\"24\" />\n <span class=\"has-text-weight-bold has-text-secondary is-size-5\">{{ schemaType() }}</span>\n @if (nodeProperty().hasError || hasError()) {\n <he-svg-icon name=\"xmark-circle\" class=\"has-text-danger is-flex is-align-items-center\" />\n <span class=\"has-text-danger has-text-weight-bold is-italic\">Error</span>\n } @else if (nodeProperty().hasWarning || hasWarning()) {\n <he-svg-icon name=\"exclamation-triangle\" class=\"has-text-warning is-flex is-align-items-center\" />\n <span class=\"has-text-warning has-text-weight-bold is-italic\">Warning</span>\n }\n }\n </div>\n @if ((showNodeLink() && nodeUrl()) || canOpen()) {\n <div class=\"is-flex is-flex-direction-row is-align-items-center is-gap-16\">\n @if (showNodeLink() && nodeUrl()) {\n <ng-container *ngTemplateOutlet=\"nodeLink; context: { url: nodeUrl(), nodeType: schemaType() }\" />\n }\n @if (canOpen()) {\n <he-svg-icon [name]=\"isOpen() ? 'minus' : 'plus'\" />\n }\n </div>\n }\n </div>\n\n <div class=\"is-flex is-flex-direction-column is-align-items-flex-start is-align-self-stretch\">\n @if (isOpen()) {\n @if (nodeProperty()?.error) {\n <ng-container\n *ngTemplateOutlet=\"\n propertyError;\n context: { property: nodeProperty(), classes: 'is-my-1 is-py-1 is-px-3' }\n \" />\n }\n }\n\n @if (unmatchedErrors().length) {\n @for (property of unmatchedErrors(); track property.id) {\n <ng-container *ngTemplateOutlet=\"propertyError; context: { property, classes: 'is-my-1 is-py-1 is-px-3' }\" />\n }\n }\n\n <div class=\"is-flex is-flex-direction-row is-flex-wrap-wrap is-align-self-stretch\">\n @for (property of properties(); track trackByProperty($index, property)) {\n <ng-container *ngTemplateOutlet=\"showProperty; context: { $implicit: property }\" />\n }\n </div>\n\n @if (isOpen()) {\n <ng-container *ngTemplateOutlet=\"propertyMap; context: { $implicit: nodeProperty() }\" />\n }\n </div>\n\n <ng-content />\n</div>\n\n<ng-template #propertyKeyTooltip let-property=\"property\">\n <markdown [data]=\"property.schema.description\" />\n</ng-template>\n\n<ng-template #propertyKey let-property=\"property\">\n @if (property.schema?.description) {\n <span\n class=\"trigger-popover | property-key\"\n [ngbTooltip]=\"propertyKeyTooltip\"\n [tooltipContext]=\"{ property }\"\n triggers=\"click\"\n autoClose=\"outside\"\n tooltipClass=\"property-tooltip\"\n placement=\"bottom-left auto\"\n container=\"body\"\n (click)=\"$event.stopPropagation()\">\n <span>{{ property.key }}</span>\n </span>\n } @else {\n <span>{{ property.key }}</span>\n }\n</ng-template>\n\n<ng-template #propertyContent let-property=\"property\">\n <div class=\"is-flex is-align-items-center is-flex-wrap-wrap is-gap-4\">\n <div class=\"is-flex has-text-secondary\">\n <ng-container *ngTemplateOutlet=\"propertyKey; context: { property }\" />\n <span>:</span>\n </div>\n\n @if (property.isCollapsible && property.showMaxLength) {\n <span class=\"is-flex is-size-7 | property-value\">\n {{ property.value | ellipsis: property.showMaxLength }}\n </span>\n\n <a class=\"is-flex is-size-7\" (click)=\"property.showMaxLength = 0\">Show more</a>\n } @else {\n <span class=\"is-flex is-size-7 | property-value\">\n {{ property.value }}\n </span>\n }\n\n @if (property.schema?.internal && property.key !== 'originalId') {\n <div class=\"property-internal\" ngbTooltip=\"This value is auto-generated.\">\n <he-svg-icon name=\"autogenerate-circle\" />\n </div>\n }\n\n <div class=\"copy-button\">\n <he-clipboard [value]=\"property.value\" [hideText]=\"true\" />\n </div>\n </div>\n\n <div class=\"is-flex is-gap-8\">\n @if (property.externalUrl?.url) {\n <a\n [href]=\"property.externalUrl.url + (property.externalUrl.urlParamValue ? property.value : '')\"\n target=\"_blank\"\n [title]=\"property.externalUrl.title\"\n class=\"ml-2\"\n [ngClass]=\"{ 'is-info': property.key === 'type' }\"\n [attr.disabled]=\"property.externalUrl.urlParamValue && !property.value ? true : null\">\n <he-svg-icon [name]=\"property.externalUrl.icon || 'external-link'\" />\n </a>\n }\n </div>\n</ng-template>\n\n<ng-template #showProperty let-property>\n @if ((isOpen() || property.closedVisible) && !property.isHidden) {\n @if (property.properties.length) {\n <div class=\"is-flex is-flex-direction-column is-flex-wrap-wrap w-100 | properties-container\">\n <div\n class=\"is-flex is-flex-direction-row is-justify-content-space-between is-gap-4 has-text-secondary w-100 | properties-container--title\"\n (click)=\"property.isOpen = !property.isOpen\"\n pointer\n [class.is-open]=\"property.isOpen\"\n [class.is-group-error-danger]=\"property.hasError\"\n [class.is-group-error-warning]=\"!property.hasError && property.hasWarning\">\n <div class=\"is-flex is-gap-8 has-text-weight-bold\">\n <!-- @if (property.schemaType === SchemaType.Term) {\n <he-node-icon [type]=\"property.schemaType\" />\n } -->\n <ng-container *ngTemplateOutlet=\"propertyKey; context: { property }\" />\n </div>\n <he-svg-icon [name]=\"property.isOpen ? 'minus' : 'plus'\" />\n </div>\n @if (property.isOpen) {\n @if (property.error) {\n <ng-container *ngTemplateOutlet=\"propertyError; context: { property, classes: 'is-my-1 is-px-3' }\" />\n }\n @if (property.isArray) {\n <div class=\"py-2 px-3 w-100\">\n @if (property.hasError) {\n <div class=\"is-flex is-align-items-center is-size-7 is-italic is-mb-2 has-text-warning\">\n <he-svg-icon name=\"exclamation-triangle\" />\n @if (showAllErrors()) {\n <a class=\"is-pl-2\" (click)=\"showAllErrors.set(false)\">Only show items in error.</a>\n } @else {\n <span class=\"is-pl-2\">Only showing items in error.</span>\n <a class=\"is-pl-1\" (click)=\"showAllErrors.set(true)\">Show all items</a>\n }\n </div>\n }\n\n @for (prop2 of property.properties; track trackByProperty($index, prop2)) {\n @if (prop2.key && (showAllErrors() || !property.hasError || prop2.hasError)) {\n <div class=\"pt-2 my-4 | property-array-container\" [id]=\"prop2.fullKey + '_' + prop2.id\">\n <div class=\"ml-2 is-mb-2 | property-array-number\">\n <span class=\"has-text-info is-px-1 | number-tag\">{{ prop2.key }}</span>\n </div>\n @if (prop2.error) {\n <ng-container\n *ngTemplateOutlet=\"propertyError; context: { property: prop2, classes: 'is-py-1 is-px-3' }\" />\n }\n <div class=\"p-3\">\n <div class=\"is-flex is-flex-direction-row is-flex-wrap-wrap is-align-self-stretch\">\n @for (prop3 of prop2.properties; track trackByProperty($index, prop3)) {\n <ng-container *ngTemplateOutlet=\"showProperty; context: { $implicit: prop3 }\" />\n }\n </div>\n </div>\n </div>\n }\n }\n </div>\n } @else {\n <div class=\"is-flex is-flex-direction-row is-flex-wrap-wrap is-align-self-stretch\">\n @for (prop2 of property.properties; track trackByProperty($index, prop2)) {\n <ng-container *ngTemplateOutlet=\"showProperty; context: { $implicit: prop2 }\" />\n }\n </div>\n }\n }\n </div>\n } @else {\n <div\n class=\"is-flex is-flex-direction-column is-gap-4 | property-container\"\n [class.is-error-danger]=\"property.hasError\"\n [class.is-error-warning]=\"!property.hasError && property.hasWarning\">\n <div\n class=\"is-flex is-flex-direction-row is-align-items-center is-justify-content-space-between is-gap-4 | field-container\">\n @if (property.key) {\n <ng-container *ngTemplateOutlet=\"propertyContent; context: { property }\" />\n }\n </div>\n @if (property.hasError || property.hasWarning) {\n <ng-container *ngTemplateOutlet=\"propertyError; context: { property }\" />\n }\n </div>\n }\n }\n</ng-template>\n\n<ng-template #propertyError let-property=\"property\" let-classes=\"classes\">\n @if (property.error?.message) {\n <div\n class=\"is-flex is-flex-direction-row is-gap-8 is-size-6 is-m-0 w-100 has-text-grey {{ classes }} | property-error\"\n [class.is-error-danger]=\"property.hasError\"\n [class.is-error-warning]=\"!property.hasError && property.hasWarning\">\n @if (property.hasError) {\n <he-svg-icon name=\"xmark-circle\" size=\"24\" class=\"has-text-danger is-flex-grow-0\" />\n } @else {\n <he-svg-icon name=\"exclamation-triangle\" size=\"24\" class=\"has-text-warning is-flex-grow-0\" />\n }\n\n <div class=\"is-flex is-flex-direction-column is-align-items-start is-flex-grow-1\">\n <span [innerHTML]=\"property.error.message\"></span>\n <he-files-error-distribution [error]=\"property.error\" />\n </div>\n\n @if (errorGuidePrefix() && property.errorGuidePageId) {\n <he-guide-overlay class=\"is-flex-grow-0\" [pageId]=\"errorGuidePrefix() + property.errorGuidePageId\" />\n }\n\n @if (property.error.index >= 0) {\n @if (property.hasWarning) {\n <a (click)=\"resolveError(property)\">\n <he-svg-icon name=\"checkmark\" />\n <span class=\"is-pl-2\">Resolved</span>\n </a>\n }\n }\n </div>\n }\n</ng-template>\n\n<ng-template #propertyMap let-property>\n @if (showMap()) {\n <div class=\"is-flex is-flex-direction-column is-flex-wrap-wrap w-100 | properties-container\">\n <div\n class=\"is-flex is-flex-direction-row is-justify-content-space-between is-gap-4 w-100 | properties-container--title\"\n (click)=\"mapVisible.set(!mapVisible())\"\n pointer\n [class.is-open]=\"mapVisible()\">\n <span class=\"is-size-6\">View on Map</span>\n <he-svg-icon [name]=\"mapVisible() ? 'minus' : 'plus'\" />\n </div>\n @if (mapVisible()) {\n <he-sites-maps [sites]=\"[node()]\" [showNotice]=\"false\" />\n }\n </div>\n }\n</ng-template>\n\n<ng-template #nodeLink let-url=\"url\" let-nodeType=\"nodeType\">\n <a class=\"external-link\" [href]=\"url\" target=\"_blank\" (click)=\"$event.stopPropagation()\">\n <span class=\"is-hidden-mobile\">View</span>\n <span class=\"is-hidden-mobile is-pl-1\">{{ nodeType }}</span>\n <he-svg-icon name=\"external-link\" class=\"ml-2\" />\n </a>\n</ng-template>\n", styles: [".notification{color:#0a0a0a!important}.notification.is-success{background-color:#d5f3d8}.notification.is-success *:not(.button):not(.icon):not(.dropdown-item)>fa-icon,.notification.is-success>fa-icon,.notification.is-success *:not(.button):not(.icon):not(.dropdown-item)>he-svg-icon,.notification.is-success>he-svg-icon{color:#2b8434}.notification.is-info{background-color:#d3ebed}.notification.is-info *:not(.button):not(.icon):not(.dropdown-item)>fa-icon,.notification.is-info>fa-icon,.notification.is-info *:not(.button):not(.icon):not(.dropdown-item)>he-svg-icon,.notification.is-info>he-svg-icon{color:#249da5}.notification.is-warning{background-color:#ffdec0}.notification.is-warning *:not(.button):not(.icon):not(.dropdown-item)>fa-icon,.notification.is-warning>fa-icon,.notification.is-warning *:not(.button):not(.icon):not(.dropdown-item)>he-svg-icon,.notification.is-warning>he-svg-icon{color:#ff881b}.notification.is-danger{background-color:#ffcdd0}.notification.is-danger *:not(.button):not(.icon):not(.dropdown-item)>fa-icon,.notification.is-danger>fa-icon,.notification.is-danger *:not(.button):not(.icon):not(.dropdown-item)>he-svg-icon,.notification.is-danger>he-svg-icon{color:#ff3844}he-sites-maps{height:200px}.external-link{color:#4c7194}.files-form-container{box-shadow:2px 2px 4px #00000029;background-color:#fff}.files-form-header{padding:10px 12px;background-color:#dbe3ea;border-top-left-radius:3px;border-top-right-radius:3px}.properties-container--title,.property-container{padding:6px 12px}.property-container{flex:none;flex-grow:1;min-width:50%;max-width:100%;border:1px solid #f5f5f5}.property-container.is-error-danger{border-color:#ff3844}.property-container.is-error-warning{border-color:#ff881b}.properties-container--title{border-top:1px solid #dbe3ea;border-bottom:1px solid #dbe3ea;background:#f5f7f9}.properties-container .properties-container{border-radius:6px;border:1px solid #dbe3ea;margin:4px 0}.properties-container .properties-container--title{border-top:none}.property-internal{color:#249da5}.property-key,.property-value{word-break:break-word}.property-array-container{border-radius:6px;border:1px solid #dbdbdb}.number-tag{border:1px solid #249da5;border-radius:50%}.copy-button{visibility:hidden}.field-container:hover .copy-button{visibility:visible}.is-group-error-danger{background-color:#ffcdd0}.is-group-error-warning{background-color:#ffdec0}.property-error.is-error-danger{background-color:#ffeced}.field-container+.property-error.is-error-danger{background-color:transparent;color:#ff3844!important}.property-error.is-error-warning{background-color:#fff5ec}.field-container+.property-error.is-error-warning{background-color:transparent;color:#ff881b!important}::ng-deep .property-tooltip{background-color:#fff;color:#0a0a0a;border:1px solid #dbdbdb;z-index:11;max-width:50vw;max-height:50vh;overflow:auto}::ng-deep .property-tooltip pre{white-space:pre-wrap}\n"] }]
14351
14944
  }], propDecorators: { isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], showNodeLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "showNodeLink", required: false }] }], errorGuidePrefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorGuidePrefix", required: false }] }], nodeErorrResolved: [{ type: i0.Output, args: ["nodeErorrResolved"] }] } });
14352
14945
 
14353
14946
  class SchemaInfoComponent {
@@ -17226,5 +17819,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
17226
17819
  * Generated bundle index. Do not edit.
17227
17820
  */
17228
17821
 
17229
- export { ARRAY_DELIMITER, ApplyPurePipe, BarChartComponent, BibliographiesSearchConfirmComponent, BlankNodeStateComponent, BlankNodeStateNoticeComponent, BlankNodeValueDeltaComponent, CapitalizePipe, ChartComponent, ChartConfigurationDirective, ChartExportButtonComponent, ChartTooltipComponent, ClickOutsideDirective, ClipboardComponent, CollapsibleBoxComponent, CollapsibleBoxStyle, ColorPalette, CompoundDirective, CompoundPipe, ContributionChartComponent, ControlValueAccessor, CycleNodesKeyGroup, CyclesCompletenessComponent, CyclesEmissionsCategoryService, CyclesEmissionsChartComponent, CyclesFunctionalUnitMeasureComponent, CyclesMetadataComponent, CyclesNodesComponent, CyclesNodesTimelineComponent, CyclesResultComponent, DataTableComponent, DefaultPipe, DeltaColour, DistributionChartComponent, DrawerContainerComponent, DurationPipe, EllipsisPipe, EngineModelsLinkComponent, EngineModelsLookupInfoComponent, EngineModelsStageComponent, EngineModelsStageDeepComponent, EngineModelsStageDeepService, EngineModelsVersionInfoComponent, EngineModelsVersionLinkComponent, EngineOrchestratorEditComponent, EngineRequirementsFormComponent, FileSizePipe, FileUploadErrorKeys, FilesDragDropDirective, FilesDropZoneComponent, FilesErrorSummaryComponent, FilesFormComponent, FilesFormEditableComponent, FilesUploadErrorsComponent, FilterAccordionComponent, FormulaBlockComponent, GUIDE_ENABLED, GetPipe, GlossaryMigrationFormat, GuideOverlayComponent, HE_API_BASE_URL, HE_CALCULATIONS_BASE_URL, HE_MAP_LOADED, HeAuthService, HeCommonService, HeEngineService, HeGlossaryService, HeMendeleyService, HeNodeCsvService, HeNodeService, HeNodeStoreService, HeSchemaService, HeSearchService, HeToastService, HorizontalBarChartComponent, HorizontalButtonsGroupComponent, ImpactAssessmentsGraphComponent, ImpactAssessmentsIndicatorBreakdownChartComponent, ImpactAssessmentsIndicatorsChartComponent, ImpactAssessmentsProductsComponent, IsArrayPipe, IsObjectPipe, IssueConfirmComponent, KeyToLabelPipe, Level, LineChartComponent, LinkKeyValueComponent, LogStatus, LongPressDirective, MAX_RESULTS, MapsDrawingComponent, MapsDrawingConfirmComponent, MaxPipe, MeanPipe, MedianPipe, MendeleySearchResult, MinPipe, MobileShellComponent, NavigationMenuComponent, NoExtPipe, NodeAggregatedComponent, NodeAggregatedFormulasComponent, NodeAggregatedInfoComponent, NodeAggregatedQualityScoreComponent, NodeAggregationLogsComponent, NodeCsvExportConfirmComponent, NodeCsvPreviewComponent, NodeCsvSelectHeadersComponent, NodeIconComponent, NodeJLogModelsComponent, NodeJsonldComponent, NodeJsonldSchemaComponent, NodeKeyState, NodeLinkComponent, NodeLogsFileComponent, NodeLogsModelsComponent, NodeLogsTimeComponent, NodeMissingLookupFactorsComponent, NodeQualityScore, NodeRecommendationsComponent, NodeSelectComponent, NodeValueDetailsComponent, PipelineStagesProgressComponent, PluralizePipe, PopoverComponent, PopoverConfirmComponent, PrecisionPipe, RelatedNodeResult, RemoveMarkdownPipe, RepeatPipe, Repository, ResizedDirective, ResizedEvent, ResponsiveService, SchemaInfoComponent, SchemaVersionLinkComponent, SearchExtendComponent, ShelfDialogComponent, ShellComponent, SiteNodesKeyGroup, SitesManagementChartComponent, SitesMapsComponent, SitesNodesComponent, SkeletonTextComponent, SocialTagsComponent, SortByPipe, SortSelectComponent, SumPipe, TagsInputDirective, Template, TermsPropertyContentComponent, TermsSubClassOfContentComponent, TermsUnitsDescriptionComponent, ThousandSuffixesPipe, ThousandsPipe, TimesPipe, ToastComponent, UncapitalizePipe, addPolygonToFeature, afterBarDrawPlugin, allCountriesQuery, allGroups, allOptions, availableProperties, axisHoverPlugin, backgroundHoverPlugin, baseApiUrl, baseUrl, bottom, buildSummary, bytesSize, calculateCycleDuration, calculateCycleDurationEnabled, calculateCycleStartDate, calculateCycleStartDateEnabled, capitalize, changelogUrl, clustererImage, code, colorToRgba, compoundToHtml, computeKeys, computeTerms, contactUsEmail, contactUsLink, convertToSvg, coordinatesToPoint, copyObject, countGroupVisibleNodes, countriesQuery, createMarker, cropsQuery, d3ellipse, d3wrap, dataPathLabel, dataPathToKey, dataVersionHeader, dataVersionHeaderKey, defaultFeature, defaultLabel, defaultSuggestionType, defaultTicksFont, definitionToSchemaType, distinctUntilChangedDeep, downloadFile, downloadPng, downloadSvg, ellipsis, engineGitBaseUrl, engineGitUrl, errorText, evaluateSuccess, exportAsSVG, exportFormats, externalLink, externalNodeLink, fillColor, fillStyle, filterBlankNode$1 as filterBlankNode, filterParams, findConfigModels, findMatchingModel, findModels, findNodeModel, findOrchestratorModel, findProperty, findPropertyById, flatFilterData, flatFilterNode, formatCustomErrorMessage, formatDate, formatError, formatPropertyError, formatter, getColor, getDatesBetween, gitBranch, gitHome, gitlabRawUrl, glossaryBaseUrl, glossaryLink, groupBlankNodesByTermIdentity, groupChanged, groupDataByCategory, groupJLogByField, groupJLogByTerm, groupLogsByTerm, groupNodesByTerm, groupdLogsByKey, grouppedKeys, grouppedValueKeys, groupsLogsByFields, guideModelUrl, guideNamespace, handleAPIError, handleGuideEvent, hasError, hasValidationError, hasWarning, hexToRgba, ignoreKeys$2 as ignoreKeys, increaseScaleLimits, initialFilterState, injectResizeEvent$, inputGroupsTermTypes, isAddPropertyEnabled, isChrome, isDateBetween, isEqual, isExternal, isKeyClosedVisible, isKeyHidden, isMaxStage, isMethodModelAllowed, isNonNodeModelKey, isSchemaIri, isScrolledBelow, isState, isTermTypeAllowed, isValidKey, jLogModelCount, keyToDataPath, levels, listColor, listColorContinuous, listColorWithAlpha, loadMapApi, locationQuery, logToCsv$2 as logToCsv, logValueArray, logsKey, lollipopChartPlugin, lookupUrl, mapFilterData, mapsUrl, markerIcon, markerPie, matchAggregatedQuery, matchAggregatedValidatedQuery, matchBoolPrefixQuery, matchCountry, matchExactQuery, matchGlobalRegion, matchId, matchNameNormalized, matchNestedKey, matchPhrasePrefixQuery, matchPhraseQuery, matchPrimaryProductQuery, matchQuery, matchRegex, matchRegion, matchTermType, matchType, maxAreaSize, measurementValue, mergeDataWithHeaders, methodTierOrder, migrationErrorMessage, migrationsUrl, modelCount, modelKeyParams, modelParams, models, multiMatchQuery, nestedProperty, nestingEnabled, nestingTypeEnabled, noValue, nodeAvailableProperties, nodeById, nodeColours$1 as nodeColours, nodeDataState, nodeDataStates, nodeDataVersion, nodeId, nodeIdWithoutDataVersion, nodeIds, nodeLink, nodeLinkEnabled, nodeLinkTypeEnabled, nodeLogsUrl, nodeQualityScoreColor, nodeQualityScoreLevel, nodeQualityScoreMaxDefault, nodeQualityScoreOrder, nodeRequestId, nodeSecondaryColours, nodeToAggregationFilename, nodeType, nodeTypeDataState, nodeTypeIcon, nodeTypeIconSchema, nodeUrl, nodeUrlParams, nodeVersion, nodeVersionKey, nodesByState, nodesByType, numberGte, optionsFromGroup, parentKey, parentProperty, parseColor, parseData, parseDataPath, parseLines, parseMessage, parseNewValue, pluralize, pointToCoordinates, polygonBounds, polygonToCoordinates, polygonToMap, polygonsFromFeature, populateWithTrackIdsFilterData, postGuideEvent, primaryProduct, productsQuery, propertyError, propertyId, recursiveProperties, refToSchemaType, refreshPropertyKeys, regionsQuery, registerChart, repeat, reportIssueLink, reportIssueUrl, safeJSONParse, safeJSONStringify, schemaBaseUrl, schemaDataBaseUrl, schemaLink, schemaRequiredProperties, schemaTypeToDefaultValue, scrollToEl, scrollTop, searchFilterData, searchableTypes, siblingProperty, simplifyContributions, singleProperty, siteTooBig, siteTypeToColor, siteTypeToIcon, sortProperties, sortedDates, strokeColor, strokeStyle, subValueKeys, suggestMatchQuery, suggestQuery, sumValues, takeAfterViewInit, termLocation, termLocationName, termProperties, termTypeLabel, toSnakeCase, toTextParts, toThousands, typeToNewProperty, typeaheadFocus, uncapitalize, uniqueDatesBetween, updateProperties, valueLink, valueToString, valueTypeToDefault, valueValue, waitFor, wildcardQuery };
17822
+ export { ARRAY_DELIMITER, ApplyPurePipe, BarChartComponent, BibliographiesSearchConfirmComponent, BlankNodeStateComponent, BlankNodeStateNoticeComponent, BlankNodeValueDeltaComponent, CapitalizePipe, ChartComponent, ChartConfigurationDirective, ChartExportButtonComponent, ChartTooltipComponent, ClickOutsideDirective, ClipboardComponent, CollapsibleBlockComponent, CollapsibleBoxComponent, CollapsibleBoxStyle, ColorPalette, CompoundDirective, CompoundPipe, ContributionChartComponent, ControlValueAccessor, CycleNodesKeyGroup, CyclesCompletenessComponent, CyclesEmissionsCategoryService, CyclesEmissionsChartComponent, CyclesFunctionalUnitMeasureComponent, CyclesMetadataComponent, CyclesNodesComponent, CyclesNodesTimelineComponent, CyclesResultComponent, DataTableComponent, DefaultPipe, DeltaColour, DistributionChartComponent, DrawerContainerComponent, DurationPipe, EllipsisPipe, EngineModelsLinkComponent, EngineModelsLookupInfoComponent, EngineModelsStageComponent, EngineModelsStageDeepComponent, EngineModelsStageDeepService, EngineModelsVersionInfoComponent, EngineModelsVersionLinkComponent, EngineOrchestratorEditComponent, EngineRequirementsFormComponent, FileSizePipe, FileUploadErrorKeys, FilesDragDropDirective, FilesDropZoneComponent, FilesErrorSummaryComponent, FilesFormComponent, FilesFormEditableComponent, FilesUploadErrorsComponent, FilterAccordionComponent, FormulaBlockComponent, GUIDE_ENABLED, GetPipe, GlossaryMigrationFormat, GuideOverlayComponent, HE_API_BASE_URL, HE_CALCULATIONS_BASE_URL, HE_MAP_LOADED, HeAuthService, HeCommonService, HeEngineService, HeGlossaryService, HeMendeleyService, HeNodeCsvService, HeNodeService, HeNodeStoreService, HeSchemaService, HeSearchService, HeToastService, HorizontalBarChartComponent, HorizontalButtonsGroupComponent, ImpactAssessmentsGraphComponent, ImpactAssessmentsIndicatorBreakdownChartComponent, ImpactAssessmentsIndicatorsChartComponent, ImpactAssessmentsProductsComponent, IsArrayPipe, IsObjectPipe, IssueConfirmComponent, KeyToLabelPipe, Level, LineChartComponent, LinkKeyValueComponent, LogStatus, LongPressDirective, MAX_RESULTS, MapsDrawingComponent, MapsDrawingConfirmComponent, MaxPipe, MeanPipe, MedianPipe, MendeleySearchResult, MinPipe, MobileShellComponent, NavigationMenuComponent, NoExtPipe, NodeAggregatedComponent, NodeAggregatedFormulasComponent, NodeAggregatedInfoComponent, NodeAggregatedQualityScoreComponent, NodeAggregationLogsComponent, NodeCsvExportConfirmComponent, NodeCsvPreviewComponent, NodeCsvSelectHeadersComponent, NodeIconComponent, NodeJLogModelsComponent, NodeJsonldComponent, NodeJsonldSchemaComponent, NodeKeyState, NodeLinkComponent, NodeLogsFileComponent, NodeLogsModelsComponent, NodeLogsTimeComponent, NodeMissingLookupFactorsComponent, NodeQualityScore, NodeRecommendationsComponent, NodeSelectComponent, NodeValueDetailsComponent, PipelineStagesProgressComponent, PluralizePipe, PopoverComponent, PopoverConfirmComponent, PrecisionPipe, RelatedNodeResult, RemoveMarkdownPipe, RepeatPipe, Repository, ResizedDirective, ResizedEvent, ResponsiveService, SchemaInfoComponent, SchemaVersionLinkComponent, SearchExtendComponent, ShelfDialogComponent, ShellComponent, SiteNodesKeyGroup, SitesManagementChartComponent, SitesMapsComponent, SitesNodesComponent, SkeletonTextComponent, SocialTagsComponent, SortByPipe, SortSelectComponent, SumPipe, TagsInputDirective, Template, TermsPropertyContentComponent, TermsSubClassOfContentComponent, TermsUnitsDescriptionComponent, ThousandSuffixesPipe, ThousandsPipe, TimesPipe, ToastComponent, UncapitalizePipe, addPolygonToFeature, afterBarDrawPlugin, allCountriesQuery, allGroups, allOptions, availableProperties, axisHoverPlugin, backgroundHoverPlugin, baseApiUrl, baseUrl, bottom, buildSummary, bytesSize, calculateCycleDuration, calculateCycleDurationEnabled, calculateCycleStartDate, calculateCycleStartDateEnabled, capitalize, changelogUrl, clustererImage, code, colorToRgba, compoundToHtml, computeKeys, computeTerms, contactUsEmail, contactUsLink, convertToSvg, coordinatesToPoint, copyObject, countGroupVisibleNodes, countriesQuery, createMarker, cropsQuery, d3ellipse, d3wrap, dataPathLabel, dataPathToKey, dataVersionHeader, dataVersionHeaderKey, defaultFeature, defaultLabel, defaultSuggestionType, defaultTicksFont, definitionToSchemaType, distinctUntilChangedDeep, downloadFile, downloadPng, downloadSvg, ellipsis, engineGitBaseUrl, engineGitUrl, errorText, evaluateSuccess, exportAsSVG, exportFormats, externalLink, externalNodeLink, fillColor, fillStyle, filterBlankNode$1 as filterBlankNode, filterParams, findConfigModels, findMatchingModel, findModels, findNodeModel, findOrchestratorModel, findProperty, findPropertyById, flatFilterData, flatFilterNode, formatCustomErrorMessage, formatDate, formatError, formatPropertyError, formatter, getColor, getDatesBetween, gitBranch, gitHome, gitlabRawUrl, glossaryBaseUrl, glossaryLink, groupBlankNodesByTermIdentity, groupChanged, groupDataByCategory, groupJLogByField, groupJLogByTerm, groupLogsByTerm, groupNodesByTerm, groupdLogsByKey, grouppedKeys, grouppedValueKeys, groupsLogsByFields, guideModelUrl, guideNamespace, handleAPIError, handleGuideEvent, hasError, hasValidationError, hasWarning, hexToRgba, ignoreKeys$2 as ignoreKeys, increaseScaleLimits, initialFilterState, injectResizeEvent$, inputGroupsTermTypes, isAddPropertyEnabled, isChrome, isDateBetween, isEqual, isExternal, isKeyClosedVisible, isKeyHidden, isMaxStage, isMethodModelAllowed, isNonNodeModelKey, isSchemaIri, isScrolledBelow, isState, isTermTypeAllowed, isValidKey, jLogModelCount, keyToDataPath, levels, listColor, listColorContinuous, listColorWithAlpha, loadMapApi, locationQuery, logToCsv$2 as logToCsv, logValueArray, logsKey, lollipopChartPlugin, lookupUrl, mapFilterData, mapsUrl, markerIcon, markerPie, matchAggregatedQuery, matchAggregatedValidatedQuery, matchBoolPrefixQuery, matchCountry, matchExactQuery, matchGlobalRegion, matchId, matchNameNormalized, matchNestedKey, matchPhrasePrefixQuery, matchPhraseQuery, matchPrimaryProductQuery, matchQuery, matchRegex, matchRegion, matchTermType, matchType, maxAreaSize, measurementValue, mergeDataWithHeaders, methodTierOrder, migrationErrorMessage, migrationsUrl, modelCount, modelKeyParams, modelParams, models, multiMatchQuery, nestedProperty, nestingEnabled, nestingTypeEnabled, noValue, nodeAvailableProperties, nodeById, nodeColours$1 as nodeColours, nodeDataState, nodeDataStates, nodeDataVersion, nodeId, nodeIdWithoutDataVersion, nodeIds, nodeLink, nodeLinkEnabled, nodeLinkTypeEnabled, nodeLogsUrl, nodeQualityScoreColor, nodeQualityScoreLevel, nodeQualityScoreMaxDefault, nodeQualityScoreOrder, nodeRequestId, nodeSecondaryColours, nodeToAggregationFilename, nodeType, nodeTypeDataState, nodeTypeIcon, nodeTypeIconSchema, nodeUrl, nodeUrlParams, nodeVersion, nodeVersionKey, nodesByState, nodesByType, numberGte, optionsFromGroup, parentKey, parentProperty, parseColor, parseData, parseDataPath, parseLines, parseMessage, parseNewValue, pluralize, pointToCoordinates, polygonBounds, polygonToCoordinates, polygonToMap, polygonsFromFeature, populateWithTrackIdsFilterData, postGuideEvent, primaryProduct, productsQuery, propertyError, propertyId, recursiveProperties, refToSchemaType, refreshPropertyKeys, regionsQuery, registerChart, repeat, reportIssueLink, reportIssueUrl, safeJSONParse, safeJSONStringify, schemaBaseUrl, schemaDataBaseUrl, schemaLink, schemaRequiredProperties, schemaTypeToDefaultValue, scrollToEl, scrollTop, searchFilterData, searchableTypes, siblingProperty, simplifyContributions, singleProperty, siteTooBig, siteTypeToColor, siteTypeToIcon, sortProperties, sortedDates, strokeColor, strokeStyle, subValueKeys, suggestMatchQuery, suggestQuery, sumValues, takeAfterViewInit, termLocation, termLocationName, termProperties, termTypeLabel, toSnakeCase, toTextParts, toThousands, typeToNewProperty, typeaheadFocus, uncapitalize, uniqueDatesBetween, updateProperties, valueLink, valueToString, valueTypeToDefault, valueValue, waitFor, wildcardQuery };
17230
17823
  //# sourceMappingURL=hestia-earth-ui-components.mjs.map