@evergis/react 4.0.161 → 4.0.164

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/components/Dashboard/componentTypes.d.ts +6 -6
  2. package/dist/components/Dashboard/constants.d.ts +25 -3
  3. package/dist/components/Dashboard/containers/FiltersContainer/utils/getFilterWidth.d.ts +2 -3
  4. package/dist/components/Dashboard/elements/ElementControl/hooks/useFirstNonEmptyValue.d.ts +6 -0
  5. package/dist/components/Dashboard/elements/ElementModal/hooks/useModalContent.d.ts +2 -2
  6. package/dist/components/Dashboard/elements/ElementTable/utils/tableBox.d.ts +2 -2
  7. package/dist/components/Dashboard/hooks/index.d.ts +2 -0
  8. package/dist/components/Dashboard/hooks/useConfigStringSources.d.ts +8 -0
  9. package/dist/components/Dashboard/hooks/useDashboardFilterStore.d.ts +9 -0
  10. package/dist/components/Dashboard/hooks/useEqualTileWidth.d.ts +1 -1
  11. package/dist/components/Dashboard/hooks/useWidgetPage.d.ts +3 -0
  12. package/dist/components/Dashboard/hooks/useWrapperSize.d.ts +3 -3
  13. package/dist/components/Dashboard/styled/containers.d.ts +5 -6
  14. package/dist/components/Dashboard/styled/index.d.ts +2 -3
  15. package/dist/components/Dashboard/types.d.ts +5 -2
  16. package/dist/components/Dashboard/utils/applyQueryFilters.d.ts +4 -3
  17. package/dist/components/Dashboard/utils/chartAxis.d.ts +1 -1
  18. package/dist/components/Dashboard/utils/configString/formatConditionString.d.ts +18 -0
  19. package/dist/components/Dashboard/utils/configString/formatters.d.ts +16 -0
  20. package/dist/components/Dashboard/utils/configString/getFilterTokenValue.d.ts +13 -0
  21. package/dist/components/Dashboard/utils/configString/index.d.ts +10 -0
  22. package/dist/components/Dashboard/utils/configString/isDashboardReference.d.ts +2 -0
  23. package/dist/components/Dashboard/utils/configString/referencesFilters.d.ts +11 -0
  24. package/dist/components/Dashboard/utils/configString/resolveConfigToken.d.ts +3 -0
  25. package/dist/components/Dashboard/utils/configString/resolveElementStrings.d.ts +8 -0
  26. package/dist/components/Dashboard/utils/configString/resolveFilterDefaults.d.ts +8 -0
  27. package/dist/components/Dashboard/utils/configString/tokenizeConfigString.d.ts +5 -0
  28. package/dist/components/Dashboard/utils/configString/types.d.ts +78 -0
  29. package/dist/components/Dashboard/utils/formatDataSourceCondition.d.ts +16 -23
  30. package/dist/components/Dashboard/utils/getChangedFilterNames.d.ts +8 -0
  31. package/dist/components/Dashboard/utils/getPageFilters.d.ts +3 -0
  32. package/dist/components/Dashboard/utils/getRenderElement.d.ts +1 -1
  33. package/dist/components/Dashboard/utils/index.d.ts +4 -0
  34. package/dist/components/Dashboard/utils/selectUpdatingDataSources.d.ts +21 -0
  35. package/dist/index.js +900 -559
  36. package/dist/index.js.map +1 -1
  37. package/dist/react.esm.js +879 -560
  38. package/dist/react.esm.js.map +1 -1
  39. package/package.json +12 -5
package/dist/react.esm.js CHANGED
@@ -4,7 +4,7 @@ import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
4
4
  import { isValidElement, Fragment, createContext, memo, useRef, useState, useCallback, useEffect, useContext, useMemo, createElement, useLayoutEffect, forwardRef } from 'react';
5
5
  import { barChartClassNames, lineChartClassNames, BarChart as BarChart$1, LineChart, PieChart } from '@evergis/charts';
6
6
  import { AttributeType, AttributeIconType, generateId, STORAGE_TOKEN_KEY, parseJwt, STORAGE_REFRESH_TOKEN_KEY, RemoteTaskStatus, AttributeConfigurationType, LayerServiceType, OgcGeometryType, StringSubType } from '@evergis/api';
7
- import { isNil, isEqual, uniqueId, uniqBy, isEmpty, unescape } from 'lodash';
7
+ import { isNil, isUndefined, isEqual, uniqueId, uniqBy, isEmpty, unescape } from 'lodash';
8
8
  import { ColorScale, Color as Color$1 } from '@evergis/color';
9
9
  import { isValid, format, parseJSON, parseISO, toDate } from 'date-fns';
10
10
  import { ru, enUS } from 'date-fns/locale';
@@ -12,9 +12,9 @@ import { stringify } from 'wkt';
12
12
  import { toMercator, bbox } from '@turf/turf';
13
13
  import { v4 } from 'uuid';
14
14
  import { HubConnectionBuilder, HttpTransportType, LogLevel } from '@microsoft/signalr';
15
+ import { returnFound } from 'find-and';
15
16
  import MapboxDraw from '@mapbox/mapbox-gl-draw';
16
17
  import { Swiper, SwiperSlide } from 'swiper/react';
17
- import { returnFound } from 'find-and';
18
18
  import ReactMarkdown from 'react-markdown';
19
19
  import rehypeRaw from 'rehype-raw';
20
20
  import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
@@ -2102,7 +2102,7 @@ function tweenValue(transition, name, value) {
2102
2102
  };
2103
2103
  }
2104
2104
 
2105
- function interpolate(a, b) {
2105
+ function interpolate$1(a, b) {
2106
2106
  var c;
2107
2107
  return (typeof b === "number" ? interpolateNumber
2108
2108
  : b instanceof color ? interpolateRgb
@@ -2177,7 +2177,7 @@ function attrFunctionNS(fullname, interpolate, value) {
2177
2177
  }
2178
2178
 
2179
2179
  function transition_attr(name, value) {
2180
- var fullname = namespace(name), i = fullname === "transform" ? interpolateTransformSvg : interpolate;
2180
+ var fullname = namespace(name), i = fullname === "transform" ? interpolateTransformSvg : interpolate$1;
2181
2181
  return this.attrTween(name, typeof value === "function"
2182
2182
  ? (fullname.local ? attrFunctionNS : attrFunction)(fullname, i, tweenValue(this, "attr." + name, value))
2183
2183
  : value == null ? (fullname.local ? attrRemoveNS : attrRemove)(fullname)
@@ -2485,7 +2485,7 @@ function styleMaybeRemove(id, name) {
2485
2485
  }
2486
2486
 
2487
2487
  function transition_style(name, value, priority) {
2488
- var i = (name += "") === "transform" ? interpolateTransformCss : interpolate;
2488
+ var i = (name += "") === "transform" ? interpolateTransformCss : interpolate$1;
2489
2489
  return value == null ? this
2490
2490
  .styleTween(name, styleNull(name, i))
2491
2491
  .on("end.style." + name, styleRemove(name))
@@ -3426,9 +3426,42 @@ const PROJECT_PROPS = [PROJECT_ALIAS_PROP, PROJECT_NAME_PROP, ""];
3426
3426
  const PROVIDER_PREFIX = "$";
3427
3427
  var ProviderPrefix;
3428
3428
  (function (ProviderPrefix) {
3429
+ /** `$card:<слой>:<поле>` — атрибут объекта карточки. */
3429
3430
  ProviderPrefix["Card"] = "card";
3430
- ProviderPrefix["Left"] = "left";
3431
+ /**
3432
+ * Ближайший дашборд, в ряд к `%` своего виджета: `$dashboard:<фильтр>[.свойство]` — его фильтр,
3433
+ * `$dashboard:<датасорс>:<поле>` — поле первой строки его датасорса.
3434
+ */
3435
+ ProviderPrefix["Dashboard"] = "dashboard";
3431
3436
  })(ProviderPrefix || (ProviderPrefix = {}));
3437
+ /**
3438
+ * Имя слоя или датасорса в подстановке: буквы, цифры, `_`, `.` и `-`. Более широкий класс захватил бы
3439
+ * соседний токен: `$dashboard:a.min–$dashboard:a.max` читался бы как датасорс `a.min–$dashboard`.
3440
+ */
3441
+ const CONFIG_SOURCE_NAME = "[\\w.-]+";
3442
+ /**
3443
+ * Все подстановки строки конфига одной грамматикой, по группам:
3444
+ * 1–2 `$dashboard:<датасорс>:<поле>`, 3–4 `$dashboard:<фильтр>[.свойство]`, 5–6 `$card:<слой>:<поле>`,
3445
+ * 7–8 `%<фильтр>[.свойство]`, 9 `{<атрибут>}`. Форма датасорса проверяется раньше формы фильтра:
3446
+ * их различает второе двоеточие. Имя фильтра — `\w+` целиком, поэтому `%name` не цепляет начало
3447
+ * `%name2`, а точка в конце предложения («за %year.») не считается свойством.
3448
+ */
3449
+ const CONFIG_TOKEN_REGEXP = new RegExp([
3450
+ `\\${PROVIDER_PREFIX}${ProviderPrefix.Dashboard}:(${CONFIG_SOURCE_NAME}):(\\w+)`,
3451
+ `\\${PROVIDER_PREFIX}${ProviderPrefix.Dashboard}:(\\w+)(?:\\.(\\w+))?`,
3452
+ `\\${PROVIDER_PREFIX}${ProviderPrefix.Card}:(${CONFIG_SOURCE_NAME}):(\\w+)`,
3453
+ `${FILTER_PREFIX}(\\w+)(?:\\.(\\w+))?`,
3454
+ "\\{([^{}\\s]+)\\}",
3455
+ ].join("|"), "g");
3456
+ /** Свойства границ диапазона: `%name.min` / `%name.max`. */
3457
+ const RANGE_PROPS = ["min", "max"];
3458
+ /** Разделитель элементов массива, подставленного в текст UI. */
3459
+ const CONFIG_TEXT_ARRAY_SEPARATOR = ", ";
3460
+ /**
3461
+ * Подписи в `options` элемента, которые проходят через подстановки строк конфига. Список явный:
3462
+ * служебные строки (`attributeName`, `chartId`, `relatedDataSource`, ...) подстановкам не подлежат.
3463
+ */
3464
+ const CONFIG_STRING_OPTION_KEYS = ["label", "placeholder", "title"];
3432
3465
  const FILTERED_VALUE_OPACITY = 28;
3433
3466
  const DEFAULT_ATTRIBUTE_NAME = "name";
3434
3467
  const DEFAULT_DATA_SOURCE_LIMIT = 100;
@@ -3483,8 +3516,8 @@ const BASE_CONTAINER_STYLE = {
3483
3516
  /**
3484
3517
  * Маркер тела контейнера внутри `ContainerRoot` — содержимое без заголовка.
3485
3518
  *
3486
- * Нужен, чтобы находить тело по DOM снаружи: `#<id контейнера>` теперь указывает на корень,
3487
- * то есть на пару «заголовок + тело».
3519
+ * Нужен, чтобы находить тело по DOM снаружи: `#<id контейнера>` указывает на корень, то есть на
3520
+ * пару «заголовок + тело».
3488
3521
  */
3489
3522
  const CONTAINER_BODY_ATTRIBUTE = "data-container-body";
3490
3523
  /**
@@ -3871,6 +3904,8 @@ const addDataSources = (dashboardConfiguration, pageIndex, layerNames) => {
3871
3904
  return newConfig.children;
3872
3905
  };
3873
3906
 
3907
+ const isEmptyValue = (value) => value === "" || value === null || value === undefined;
3908
+
3874
3909
  const transparentizeColor = (originalColor, percent) => {
3875
3910
  const color = new Color$1(originalColor);
3876
3911
  color.a = Math.round((255 / 100) * percent);
@@ -4210,7 +4245,7 @@ const formatDateValue = (stringFormat, dateValue) => {
4210
4245
  const timeOption = getValueFromOptions(timeOptions, stringFormat.format);
4211
4246
  const timeOptionValue = timeOption && (isRuLocale ? timeOption.value : `${timeOption.value} aa`);
4212
4247
  const timeFormat = (timeOptionValue && format(value, showTimeZone ? `${timeOptionValue} zz` : timeOptionValue)) || "";
4213
- return dateFormat ? `${dateFormat} ${timeFormat}` : "";
4248
+ return dateFormat ? [dateFormat, timeFormat].filter(Boolean).join(" ") : "";
4214
4249
  };
4215
4250
  const appendUnitsLabel = (value, unitsLabel, noUnits) => (unitsLabel && !noUnits ? `${value} ${unitsLabel}` : value)?.toString() || "";
4216
4251
  const formatNumberValue = (stringFormat, value, type, noUnits = false) => {
@@ -4231,8 +4266,8 @@ const formatNumberValue = (stringFormat, value, type, noUnits = false) => {
4231
4266
  if (typeof compactValue === "string" && compactValue) {
4232
4267
  return appendUnitsLabel(compactValue, unitsLabel, noUnits);
4233
4268
  }
4234
- if (hasRounding && (!isIntValue || !isDefaultScaling)) {
4235
- currentValue = currentValue && Number(currentValue).toFixed(rounding);
4269
+ if (hasRounding && (!isIntValue || !isDefaultScaling) && !isNil(currentValue)) {
4270
+ currentValue = Number(currentValue).toFixed(rounding);
4236
4271
  }
4237
4272
  if (splitDigitGroup) {
4238
4273
  currentValue = numberWithSpaces(currentValue);
@@ -4353,6 +4388,49 @@ const metersPerPixel = (zoom, lat) => {
4353
4388
  return (156543.03 * Math.cos(lat * (Math.PI / 180))) / 2 ** zoom;
4354
4389
  };
4355
4390
 
4391
+ const checkIsDateFormat = (value) => value.startsWith("#'") && value.endsWith("'");
4392
+ const checkIfNeedQuotes = (value, attributeType) => {
4393
+ return attributeType
4394
+ ? !NUMERIC_ATTRIBUTE_TYPES.includes(attributeType)
4395
+ : typeof value === "number"
4396
+ ? false
4397
+ : typeof value === "string" && !isNumeric(value) && !checkIsDateFormat(value);
4398
+ };
4399
+ const formatConditionValue = ({ value, attributeType, defaultValue, checkQuotes = true, isSetParams = false, }) => {
4400
+ if (isEmptyValue(value))
4401
+ return "";
4402
+ const formattedValue = Array.isArray(defaultValue) && !Array.isArray(value) ? [value] : value;
4403
+ const isArray = Array.isArray(formattedValue);
4404
+ let stringValue = formattedValue.toString();
4405
+ if (isArray) {
4406
+ return formattedValue.length && !formattedValue.every(item => !item)
4407
+ ? `[${formattedValue
4408
+ .map(item => (checkQuotes && checkIfNeedQuotes(item, attributeType) ? `'${item}'` : item))
4409
+ .join(",")}]`
4410
+ : "";
4411
+ }
4412
+ if (isSetParams && checkIsDateFormat(stringValue)) {
4413
+ stringValue = stringValue.slice(2, -1);
4414
+ }
4415
+ return checkQuotes && checkIfNeedQuotes(stringValue, attributeType) ? `'${stringValue}'` : stringValue;
4416
+ };
4417
+
4418
+ /**
4419
+ * Значение системной подстановки текущего проекта. Голый `%project` и `%project.name` дают
4420
+ * системное имя, `%project.alias` — алиас с фолбэком на имя: пустой алиас не должен уводить
4421
+ * запрос в пустое значение. Чужое свойство (`%project.foo`) не наше — вернём `undefined`,
4422
+ * и плейсхолдер останется нетронутым, как у незаполненного фильтра.
4423
+ */
4424
+ const getProjectValue = ({ prop, projectName, projectAlias, }) => {
4425
+ if (prop && prop !== PROJECT_NAME_PROP && prop !== PROJECT_ALIAS_PROP) {
4426
+ return undefined;
4427
+ }
4428
+ if (prop === PROJECT_ALIAS_PROP) {
4429
+ return projectAlias || projectName || undefined;
4430
+ }
4431
+ return projectName || undefined;
4432
+ };
4433
+
4356
4434
  /** Ключ уровня в значении tree-фильтра: `l1`, `l2`, ... */
4357
4435
  const LEVEL_KEY = /^l\d+$/;
4358
4436
  /**
@@ -4402,22 +4480,6 @@ const getDataSourceFilterValue = ({ filterName, filterProp, attributeAlias, data
4402
4480
  return feature?.properties?.[filterProp];
4403
4481
  };
4404
4482
 
4405
- /**
4406
- * Значение системной подстановки текущего проекта. Голый `%project` и `%project.name` дают
4407
- * системное имя, `%project.alias` — алиас с фолбэком на имя: пустой алиас не должен уводить
4408
- * запрос в пустое значение. Чужое свойство (`%project.foo`) не наше — вернём `undefined`,
4409
- * и плейсхолдер останется нетронутым, как у незаполненного фильтра.
4410
- */
4411
- const getProjectValue = ({ prop, projectName, projectAlias, }) => {
4412
- if (prop && prop !== PROJECT_NAME_PROP && prop !== PROJECT_ALIAS_PROP) {
4413
- return undefined;
4414
- }
4415
- if (prop === PROJECT_ALIAS_PROP) {
4416
- return projectAlias || projectName || undefined;
4417
- }
4418
- return projectName || undefined;
4419
- };
4420
-
4421
4483
  const getSelectedFilterValue = (filterName, selectedFilters, defaultValue) => {
4422
4484
  return ((!isNil(selectedFilters?.[filterName]?.value) &&
4423
4485
  Array.isArray(defaultValue) &&
@@ -4426,124 +4488,370 @@ const getSelectedFilterValue = (filterName, selectedFilters, defaultValue) => {
4426
4488
  : selectedFilters?.[filterName]?.value) ?? defaultValue);
4427
4489
  };
4428
4490
 
4429
- const applyQueryFilters = ({ parameters: configParameters, filters: configFilters, selectedFilters, geometry, extent, zoomLevel, projectName, projectAlias, attributes, layerInfo, dataSources, projectDataSources, }) => {
4491
+ const known = (value, defaultValue) => ({ known: true, value, defaultValue });
4492
+ const getRangeBound = (prop, defaultValue, scalarDefault) => {
4493
+ if (Array.isArray(defaultValue)) {
4494
+ return defaultValue[prop === "min" ? 0 : 1];
4495
+ }
4496
+ return scalarDefault ? defaultValue : undefined;
4497
+ };
4498
+ /** `.prop` — свойство строки `relatedDataSource`, выбранной фильтром. */
4499
+ const getRelatedValue = ({ name, prop, store, configFilter, defaultValue }) => getDataSourceFilterValue({
4500
+ filterName: name,
4501
+ filterProp: prop,
4502
+ selectedFilters: store.selectedFilters ?? {},
4503
+ attributeAlias: configFilter?.attributeAlias,
4504
+ dataSource: getDataSource(configFilter?.relatedDataSource, store.dataSources),
4505
+ }) ?? defaultValue;
4506
+ const getTreeLevel = (value, prop) => {
4507
+ const level = value[prop];
4508
+ return isNil(level) || !level.length ? undefined : level;
4509
+ };
4510
+ /** Параметры запроса и текст UI: пустое (`null`/`undefined`) значение падает в дефолт. */
4511
+ const getValueModeToken = (context) => {
4512
+ const { name, prop, store, configFilter, selected, defaultValue } = context;
4513
+ if (RANGE_PROPS.includes(prop)) {
4514
+ return known(selected?.[prop] ?? getRangeBound(prop, defaultValue, true));
4515
+ }
4516
+ if (prop) {
4517
+ return isTreeFilterValue(selected?.value, configFilter)
4518
+ ? known(getTreeLevel(selected.value, prop))
4519
+ : known(getRelatedValue(context));
4520
+ }
4521
+ return known(getSelectedFilterValue(name, store.selectedFilters, defaultValue), defaultValue);
4522
+ };
4523
+ /** Условие слоя: выбранное пустое значение не подставляется, объектные значения — только уровнями tree. */
4524
+ const getConditionModeToken = (context) => {
4525
+ const { prop, configFilter, selected, defaultValue } = context;
4526
+ const rawValue = selected !== undefined ? selected.value : defaultValue;
4527
+ if (configFilter?.valueType === "features" || isFeaturesFilterValue(rawValue)) {
4528
+ return { known: false };
4529
+ }
4530
+ if (isTreeFilterValue(rawValue, configFilter)) {
4531
+ return prop ? known(getTreeLevel(rawValue, prop)) : { known: false };
4532
+ }
4533
+ const hasValue = selected !== undefined && !!selected.value?.toString().length;
4534
+ if (RANGE_PROPS.includes(prop)) {
4535
+ return known(hasValue ? selected[prop] : getRangeBound(prop, defaultValue, false));
4536
+ }
4537
+ if (prop) {
4538
+ return known(getRelatedValue(context));
4539
+ }
4540
+ return known(hasValue ? selected.value : selected === undefined ? defaultValue : "", defaultValue);
4541
+ };
4542
+ /**
4543
+ * Значение токена фильтра (`%name[.prop]`, `$dashboard:name[.prop]`) из переданного стора.
4544
+ * Фильтр, о котором стор ничего не знает, — `known: false`: токен остаётся в строке как есть.
4545
+ */
4546
+ const getFilterTokenValue = (params) => {
4547
+ const { name, store, mode } = params;
4548
+ const configFilter = getConfigFilter(name, store.configFilters);
4549
+ const selected = store.selectedFilters?.[name];
4550
+ if (!configFilter && (mode === "condition" || selected === undefined)) {
4551
+ return { known: false };
4552
+ }
4553
+ const context = { ...params, configFilter, selected, defaultValue: configFilter?.defaultValue };
4554
+ return mode === "condition" ? getConditionModeToken(context) : getValueModeToken(context);
4555
+ };
4556
+
4557
+ const UNKNOWN = { known: false };
4558
+ /** Стор `$dashboard:`. Без отдельного стора строка живёт на самом дашборде — это стор виджета. */
4559
+ const getDashboardStore = ({ dashboard, widget }) => dashboard ?? widget ?? {};
4560
+ const isValidGeometry = (geometry) => !!geometry && !geometry.includes("()") && geometry.endsWith(")");
4561
+ /**
4562
+ * Системные подстановки `%geometry`, `%extent`, `%zoom`, `%project[.name|.alias]` — значения из
4563
+ * `GlobalContext`, а не из фильтров. Пока значения нет, `geometry` / `extent` / `zoom` уступают
4564
+ * одноимённому фильтру; имя `project` зарезервировано целиком.
4565
+ */
4566
+ const resolveSystemToken = ({ name, prop }, sources) => {
4567
+ const { geometry, extent, zoomLevel, projectName, projectAlias } = sources;
4568
+ if (name === GEOMETRY_FILTER_NAME && !prop && isValidGeometry(geometry)) {
4569
+ return { known: true, value: geometry, system: "quoted" };
4570
+ }
4571
+ if (name === EXTENT_FILTER_NAME && !prop && extent) {
4572
+ return { known: true, value: extent, system: "quoted" };
4573
+ }
4574
+ if (name === ZOOM_FILTER_NAME && !prop && !isNil(zoomLevel)) {
4575
+ return { known: true, value: zoomLevel, system: "number" };
4576
+ }
4577
+ if (name === PROJECT_FILTER_NAME) {
4578
+ const value = getProjectValue({ prop, projectName, projectAlias });
4579
+ return isNil(value) ? UNKNOWN : { known: true, value, system: "quoted" };
4580
+ }
4581
+ return null;
4582
+ };
4583
+ const resolveFilterToken = (token, sources, mode) => {
4584
+ const { scope, name, prop } = token;
4585
+ if (scope === "widget") {
4586
+ const system = resolveSystemToken(token, sources);
4587
+ if (system)
4588
+ return system;
4589
+ }
4590
+ const store = scope === "dashboard" ? getDashboardStore(sources) : sources.widget ?? {};
4591
+ return getFilterTokenValue({ name, prop, store, mode });
4592
+ };
4593
+ /** `$card:<слой>:<поле>` — атрибут объекта карточки, если карточка открыта на этом слое. */
4594
+ const resolveCardToken = ({ layerName, fieldName }, { attributes, layerInfo }) => {
4595
+ if (!attributes || layerInfo?.name !== layerName)
4596
+ return UNKNOWN;
4597
+ const attribute = attributes.find(({ attributeName }) => attributeName === fieldName);
4598
+ return { known: true, value: attribute?.value ?? null, attributeType: attribute?.type };
4599
+ };
4600
+ /**
4601
+ * `$dashboard:<датасорс>:<поле>` — поле первой строки датасорса дашборда (по имени, затем по
4602
+ * `layerName`). Пока датасорс не загружен, значение пустое: токен не должен мелькать в тексте.
4603
+ */
4604
+ const resolveDataSourceToken = ({ dataSourceName, fieldName }, sources) => {
4605
+ const { dataSources } = getDashboardStore(sources);
4606
+ const dataSource = dataSources?.find(({ name }) => name === dataSourceName) ??
4607
+ dataSources?.find(({ layerName }) => layerName === dataSourceName);
4608
+ return { known: true, value: dataSource?.features?.[0]?.properties?.[fieldName] ?? null };
4609
+ };
4610
+ /** `{name}` — атрибут объекта; пустой атрибут даёт пустую строку, незнакомое имя остаётся как есть. */
4611
+ const resolveAttributeToken = ({ name }, { attributes }) => {
4612
+ const attribute = attributes?.find(({ attributeName }) => attributeName === name);
4613
+ return attribute ? { known: true, value: attribute.value ?? "", attributeType: attribute.type } : UNKNOWN;
4614
+ };
4615
+ /** Сырое значение токена строки конфига — без форматирования под параметр, условие или текст. */
4616
+ const resolveConfigToken = (token, sources, mode) => {
4617
+ if (token.kind === "filter")
4618
+ return resolveFilterToken(token, sources, mode);
4619
+ if (token.kind === "card")
4620
+ return resolveCardToken(token, sources);
4621
+ if (token.kind === "dataSource")
4622
+ return resolveDataSourceToken(token, sources);
4623
+ return resolveAttributeToken(token, sources);
4624
+ };
4625
+
4626
+ const toToken = ([raw, ...groups]) => {
4627
+ const [dataSourceName, dataSourceField, dashboardFilter, dashboardProp, cardLayer, cardField, filterName, filterProp, attributeName,] = groups;
4628
+ if (dataSourceName) {
4629
+ return { kind: "dataSource", dataSourceName, fieldName: dataSourceField, raw };
4630
+ }
4631
+ if (dashboardFilter) {
4632
+ return { kind: "filter", scope: "dashboard", name: dashboardFilter, prop: dashboardProp, raw };
4633
+ }
4634
+ if (cardLayer) {
4635
+ return { kind: "card", layerName: cardLayer, fieldName: cardField, raw };
4636
+ }
4637
+ if (filterName) {
4638
+ return { kind: "filter", scope: "widget", name: filterName, prop: filterProp, raw };
4639
+ }
4640
+ return { kind: "attribute", name: attributeName, raw };
4641
+ };
4642
+ /** Разбирает строку конфига на литералы и токены подстановок — в исходном порядке. */
4643
+ const tokenizeConfigString = (value) => {
4644
+ const segments = [];
4645
+ let lastIndex = 0;
4646
+ for (const match of value.matchAll(CONFIG_TOKEN_REGEXP)) {
4647
+ if (match.index > lastIndex) {
4648
+ segments.push(value.slice(lastIndex, match.index));
4649
+ }
4650
+ segments.push(toToken(match));
4651
+ lastIndex = match.index + match[0].length;
4652
+ }
4653
+ if (lastIndex < value.length) {
4654
+ segments.push(value.slice(lastIndex));
4655
+ }
4656
+ return segments;
4657
+ };
4658
+ /** Строка целиком — один токен: тогда значение уходит как есть, с типом (число, массив). */
4659
+ const getSingleToken = (segments) => segments.length === 1 && typeof segments[0] !== "string" ? segments[0] : null;
4660
+
4661
+ /** Склейка сегментов: токен заменяется своим форматом, незнакомый — остаётся как написан. */
4662
+ const joinConfigSegments = (segments, formatToken) => segments.map(segment => (typeof segment === "string" ? segment : formatToken(segment))).join("");
4663
+ const hasTokens = (segments) => segments.some(segment => typeof segment !== "string");
4664
+ const toParameterText = (value) => (isNil(value) ? "" : String(value));
4665
+ const toUiText = (value) => {
4666
+ if (isNil(value) || isObject(value) && !(value instanceof Date))
4667
+ return "";
4668
+ if (Array.isArray(value)) {
4669
+ return value
4670
+ .map(toUiText)
4671
+ .filter(item => item !== "")
4672
+ .join(CONFIG_TEXT_ARRAY_SEPARATOR);
4673
+ }
4674
+ return String(value);
4675
+ };
4676
+ const interpolate = (segments, sources, toText) => joinConfigSegments(segments, token => {
4677
+ const resolved = resolveConfigToken(token, sources, "value");
4678
+ return resolved.known ? toText(resolved.value) : token.raw;
4679
+ });
4680
+ /**
4681
+ * Значение параметра запроса. Строка-токен целиком уходит сырым значением с типом, а пустой или
4682
+ * незнакомый фильтр не уходит вовсе (`undefined`). Токены внутри строки интерполируются текстом.
4683
+ */
4684
+ const resolveQueryParameter = (value, sources) => {
4685
+ if (typeof value !== "string")
4686
+ return value;
4687
+ const segments = tokenizeConfigString(value);
4688
+ const token = getSingleToken(segments);
4689
+ if (token) {
4690
+ const resolved = resolveConfigToken(token, sources, "value");
4691
+ // Незнакомое имя в фигурных скобках — не подстановка, а литерал (JSON, шаблон сервиса).
4692
+ if (!resolved.known && token.kind === "attribute")
4693
+ return value;
4694
+ return resolved.known && !isNil(resolved.value) ? resolved.value : undefined;
4695
+ }
4696
+ return hasTokens(segments) ? interpolate(segments, sources, toParameterText) : value;
4697
+ };
4698
+ /**
4699
+ * Строка конфига для UI (значение элемента, дефолт, подпись). Строка-токен целиком отдаёт сырое
4700
+ * значение (число, массив — например, дефолт мультиселекта); пустое — пустая строка. Внутри текста
4701
+ * массив склеивается через `", "`. Незнакомый токен (опечатка, литерал `50%off`) остаётся как написан.
4702
+ */
4703
+ const resolveConfigString = (value, sources) => {
4704
+ if (typeof value !== "string" || !value)
4705
+ return value;
4706
+ const segments = tokenizeConfigString(value);
4707
+ const token = getSingleToken(segments);
4708
+ if (token) {
4709
+ const resolved = resolveConfigToken(token, sources, "value");
4710
+ if (!resolved.known)
4711
+ return value;
4712
+ return isNil(resolved.value) || isObject(resolved.value) && !(resolved.value instanceof Date) ? "" : resolved.value;
4713
+ }
4714
+ return hasTokens(segments) ? interpolate(segments, sources, toUiText) : value;
4715
+ };
4716
+
4717
+ const SET_PARAMS_REGEXP = /\$\([^)]+\)/g;
4718
+ const CONDITION_SPLITTER = " AND ";
4719
+ const formatResolvedToken = (resolved, isSetParams) => {
4720
+ if (!resolved.known)
4721
+ return "";
4722
+ const { value, system, attributeType, defaultValue } = resolved;
4723
+ if (system === "quoted")
4724
+ return `'${value}'`;
4725
+ if (system === "number")
4726
+ return String(value);
4727
+ return formatConditionValue({
4728
+ value: (attributeType ? value?.toString() : value),
4729
+ attributeType,
4730
+ defaultValue,
4731
+ isSetParams,
4732
+ });
4733
+ };
4734
+ /**
4735
+ * Eql-параметры слоя подставляются по голому имени. Имя внутри `$(имя=` прячется на время замены,
4736
+ * чтобы не задеть объявление set-параметра.
4737
+ */
4738
+ const applyEqlParameters = (section, { eqlParameters, layerParams }) => {
4739
+ if (!eqlParameters || !layerParams)
4740
+ return section;
4741
+ return Object.keys(eqlParameters).reduce((result, param) => result
4742
+ .replace(new RegExp(`\\$\\(${param}=`), "***")
4743
+ .replace(new RegExp(param), formatConditionValue({ value: layerParams[param] }))
4744
+ .replace(/\*\*\*/, `$(${param}=`), section);
4745
+ };
4746
+ /**
4747
+ * Одна секция условия: все токены в формате условия. Пустое значение фильтра оставляет токен как
4748
+ * есть; пустой атрибут объекта (`{name}`) заменяется пустой строкой.
4749
+ */
4750
+ const formatConditionSection = (section, sources, { isSetParams = false, ...eql } = {}) => {
4751
+ const formatted = joinConfigSegments(tokenizeConfigString(section), (token) => {
4752
+ const resolved = resolveConfigToken(token, sources, "condition");
4753
+ const value = formatResolvedToken(resolved, isSetParams);
4754
+ return value || (token.kind === "attribute" && resolved.known ? "" : token.raw);
4755
+ });
4756
+ return applyEqlParameters(formatted, eql);
4757
+ };
4758
+ /**
4759
+ * Условие слоя: ведущие set-параметры `$(имя=…)` форматируются отдельно (даты без `#'…'`),
4760
+ * остаток — по секциям `AND`.
4761
+ */
4762
+ const formatConditionString = (condition, sources, eql = {}) => {
4763
+ const setParams = condition.match(SET_PARAMS_REGEXP) ?? [];
4764
+ const setParamsSection = setParams.map(section => formatConditionSection(section, sources, { ...eql, isSetParams: true }));
4765
+ const conditionSection = condition
4766
+ .substring(setParams.join("").length)
4767
+ .split(CONDITION_SPLITTER)
4768
+ .map(section => formatConditionSection(section, sources, eql));
4769
+ if (!setParamsSection.length)
4770
+ return conditionSection.join(CONDITION_SPLITTER);
4771
+ return conditionSection.length
4772
+ ? [setParamsSection.join(""), conditionSection.join(CONDITION_SPLITTER)].join(" ")
4773
+ : setParamsSection.join("");
4774
+ };
4775
+
4776
+ const DASHBOARD_TOKEN_PREFIX = `${PROVIDER_PREFIX}${ProviderPrefix.Dashboard}:`;
4777
+ /** Строка ссылается на фильтр дашборда (`$dashboard:`). */
4778
+ const isDashboardReference = (value) => typeof value === "string" && value.includes(DASHBOARD_TOKEN_PREFIX);
4779
+
4780
+ /**
4781
+ * Строка конфига ссылается на один из фильтров: `%name[.prop]` — на фильтр виджета,
4782
+ * `$dashboard:name[.prop]` — на фильтр дашборда. На самом дашборде (`hasDashboardStore = false`)
4783
+ * `$dashboard:` — те же фильтры виджета.
4784
+ */
4785
+ const referencesFilters = ({ value, widgetNames, dashboardNames, hasDashboardStore, }) => typeof value === "string" &&
4786
+ tokenizeConfigString(value).some(segment => {
4787
+ if (typeof segment === "string" || segment.kind !== "filter")
4788
+ return false;
4789
+ const names = segment.scope === "dashboard" && hasDashboardStore ? dashboardNames : widgetNames;
4790
+ return names.includes(segment.name);
4791
+ });
4792
+
4793
+ /**
4794
+ * Узел конфига с подставленными строками: `value`, `defaultValue` и подписи из
4795
+ * {@link CONFIG_STRING_OPTION_KEYS}. Без подстановок узел возвращается тем же объектом —
4796
+ * мемоизированные контейнеры не перерисовываются зря.
4797
+ */
4798
+ const resolveElementStrings = (element, sources) => {
4799
+ if (!element || !sources)
4800
+ return element;
4801
+ const value = resolveConfigString(element.value, sources);
4802
+ const defaultValue = resolveConfigString(element.defaultValue, sources);
4803
+ const changedOptions = CONFIG_STRING_OPTION_KEYS.reduce((result, key) => {
4804
+ const option = element.options?.[key];
4805
+ const resolved = resolveConfigString(option, sources);
4806
+ return resolved === option ? result : { ...result, [key]: resolved };
4807
+ }, {});
4808
+ const hasChangedOptions = Object.keys(changedOptions).length > 0;
4809
+ if (value === element.value && defaultValue === element.defaultValue && !hasChangedOptions) {
4810
+ return element;
4811
+ }
4812
+ return {
4813
+ ...element,
4814
+ value,
4815
+ defaultValue,
4816
+ ...(hasChangedOptions && { options: { ...element.options, ...changedOptions } }),
4817
+ };
4818
+ };
4819
+
4820
+ /**
4821
+ * Дефолты фильтров виджета из фильтров дашборда: `defaultValue: "$dashboard:year"`. Резолвится
4822
+ * только `$dashboard:` — ссылки на собственные фильтры (`%name`) дали бы цикл и остаются как есть.
4823
+ * Дефолт живой: пока фильтр виджета не выбран, он следует за дашбордом, ручной выбор его перекрывает.
4824
+ */
4825
+ const resolveFilterDefaults = (configFilters, dashboard) => {
4826
+ if (!dashboard || !configFilters?.some(({ defaultValue }) => isDashboardReference(defaultValue))) {
4827
+ return configFilters;
4828
+ }
4829
+ return configFilters.map(filter => {
4830
+ if (!isDashboardReference(filter.defaultValue))
4831
+ return filter;
4832
+ const defaultValue = resolveQueryParameter(filter.defaultValue, { widget: {}, dashboard });
4833
+ return defaultValue === filter.defaultValue ? filter : { ...filter, defaultValue: defaultValue };
4834
+ });
4835
+ };
4836
+
4837
+ const applyQueryFilters = ({ parameters: configParameters, filters: configFilters, selectedFilters, dashboard, geometry, extent, zoomLevel, projectName, projectAlias, attributes, layerInfo, dataSources, }) => {
4430
4838
  if (!configParameters) {
4431
4839
  return {};
4432
4840
  }
4841
+ const sources = {
4842
+ widget: { selectedFilters, configFilters, dataSources },
4843
+ dashboard,
4844
+ geometry,
4845
+ extent,
4846
+ zoomLevel,
4847
+ projectName,
4848
+ projectAlias,
4849
+ attributes,
4850
+ layerInfo,
4851
+ };
4433
4852
  return Object.keys(configParameters).reduce((result, key) => {
4434
- if (typeof configParameters[key] === "string" && configParameters[key].startsWith(PROVIDER_PREFIX)) {
4435
- const raw = configParameters[key].slice(PROVIDER_PREFIX.length);
4436
- const [provider, layerName, fieldName] = raw.split(":");
4437
- if (provider === ProviderPrefix.Card && fieldName && layerInfo.name === layerName && attributes) {
4438
- const attribute = attributes.find(({ attributeName }) => attributeName === fieldName);
4439
- const cardValue = attribute?.value ?? null;
4440
- if (isNil(cardValue))
4441
- return result;
4442
- return { ...result, [key]: cardValue };
4443
- }
4444
- if (provider === ProviderPrefix.Left && layerName && fieldName && projectDataSources?.length) {
4445
- const dataSource = projectDataSources.find(({ layerName: name }) => name === layerName);
4446
- const leftValue = dataSource?.features?.[0]?.properties?.[fieldName] ?? null;
4447
- if (isNil(leftValue))
4448
- return result;
4449
- return { ...result, [key]: leftValue };
4450
- }
4451
- return result;
4452
- }
4453
- if (typeof configParameters[key] === "string" && configParameters[key].includes("{") && attributes?.length) {
4454
- const exactAttr = attributes.find(({ attributeName }) => configParameters[key] === `{${attributeName}}`);
4455
- if (exactAttr) {
4456
- return { ...result, [key]: exactAttr.value ?? "" };
4457
- }
4458
- let interpolated = configParameters[key];
4459
- attributes.forEach(({ attributeName, value: attrValue }) => {
4460
- interpolated = interpolated.replace(new RegExp(`\\{${attributeName}\\}`, "g"), attrValue?.toString() ?? "");
4461
- });
4462
- return { ...result, [key]: interpolated };
4463
- }
4464
- if (typeof configParameters[key] !== "string" || !configParameters[key].startsWith("%")) {
4465
- return {
4466
- ...result,
4467
- [key]: configParameters[key],
4468
- };
4469
- }
4470
- const filterFullName = configParameters[key].replace("%", "");
4471
- const [filterName, filterProp] = filterFullName.includes(".") ? filterFullName.split(".") : [filterFullName, null];
4472
- const configFilter = getConfigFilter(filterName, configFilters);
4473
- const { defaultValue, relatedDataSource, attributeAlias } = configFilter || {};
4474
- if (filterName === GEOMETRY_FILTER_NAME && geometry && !geometry.includes("()") && geometry.endsWith(")")) {
4475
- return {
4476
- ...result,
4477
- [key]: geometry,
4478
- };
4479
- }
4480
- // Системные фильтры вида карты: значение приходит из `GlobalContext`, а не из `SelectedFilters`,
4481
- // поэтому резолвятся до веток `.min` / `.max` / `.property` — как `geometry` выше.
4482
- if (filterName === EXTENT_FILTER_NAME && extent) {
4483
- return {
4484
- ...result,
4485
- [key]: extent,
4486
- };
4487
- }
4488
- // Единственная нестроковая системная подстановка: уровень зума уходит числом.
4489
- if (filterName === ZOOM_FILTER_NAME && !isNil(zoomLevel)) {
4490
- return {
4491
- ...result,
4492
- [key]: zoomLevel,
4493
- };
4494
- }
4495
- // Текущий проект: значение приходит из `GlobalContext`, а не из `SelectedFilters`. Резолвим
4496
- // до веток `.min` / `.max` / `.property`, как экстент и зум, — имя `project` зарезервировано.
4497
- if (filterName === PROJECT_FILTER_NAME) {
4498
- const projectValue = getProjectValue({ prop: filterProp, projectName, projectAlias });
4499
- if (isNil(projectValue)) {
4500
- return result;
4501
- }
4502
- return {
4503
- ...result,
4504
- [key]: projectValue,
4505
- };
4506
- }
4507
- if (configParameters[key].endsWith(".max")) {
4508
- return {
4509
- ...result,
4510
- [key]: selectedFilters?.[filterName]?.max ?? (Array.isArray(defaultValue) ? defaultValue[1] : defaultValue),
4511
- };
4512
- }
4513
- if (configParameters[key].endsWith(".min")) {
4514
- return {
4515
- ...result,
4516
- [key]: selectedFilters?.[filterName]?.min ?? (Array.isArray(defaultValue) ? defaultValue[0] : defaultValue),
4517
- };
4518
- }
4519
- if (configParameters[key].includes(".")) {
4520
- const selectedValue = selectedFilters?.[filterName]?.value;
4521
- if (isTreeFilterValue(selectedValue, configFilter)) {
4522
- const levelValue = filterProp ? selectedValue[filterProp] : undefined;
4523
- if (isNil(levelValue) || !levelValue.length) {
4524
- return result;
4525
- }
4526
- return { ...result, [key]: levelValue };
4527
- }
4528
- return {
4529
- ...result,
4530
- [key]: getDataSourceFilterValue({
4531
- filterName,
4532
- filterProp,
4533
- selectedFilters,
4534
- attributeAlias,
4535
- dataSource: getDataSource(relatedDataSource, dataSources),
4536
- }) ?? defaultValue,
4537
- };
4538
- }
4539
- const value = getSelectedFilterValue(filterName, selectedFilters, defaultValue);
4540
- if (isNil(value)) {
4541
- return result;
4542
- }
4543
- return {
4544
- ...result,
4545
- [key]: value,
4546
- };
4853
+ const value = resolveQueryParameter(configParameters[key], sources);
4854
+ return isUndefined(value) ? result : { ...result, [key]: value };
4547
4855
  }, {});
4548
4856
  };
4549
4857
 
@@ -4598,35 +4906,6 @@ const createConfigPage = (props) => {
4598
4906
  };
4599
4907
  };
4600
4908
 
4601
- const isEmptyValue = (value) => value === "" || value === null || value === undefined;
4602
-
4603
- const checkIsDateFormat = (value) => value.startsWith("#'") && value.endsWith("'");
4604
- const checkIfNeedQuotes = (value, attributeType) => {
4605
- return attributeType
4606
- ? !NUMERIC_ATTRIBUTE_TYPES.includes(attributeType)
4607
- : typeof value === "number"
4608
- ? false
4609
- : typeof value === "string" && !isNumeric(value) && !checkIsDateFormat(value);
4610
- };
4611
- const formatConditionValue = ({ value, attributeType, defaultValue, checkQuotes = true, isSetParams = false, }) => {
4612
- if (isEmptyValue(value))
4613
- return "";
4614
- const formattedValue = Array.isArray(defaultValue) && !Array.isArray(value) ? [value] : value;
4615
- const isArray = Array.isArray(formattedValue);
4616
- let stringValue = formattedValue.toString();
4617
- if (isArray) {
4618
- return formattedValue.length && !formattedValue.every(item => !item)
4619
- ? `[${formattedValue
4620
- .map(item => (checkQuotes && checkIfNeedQuotes(item, attributeType) ? `'${item}'` : item))
4621
- .join(",")}]`
4622
- : "";
4623
- }
4624
- if (isSetParams && checkIsDateFormat(stringValue)) {
4625
- stringValue = stringValue.slice(2, -1);
4626
- }
4627
- return checkQuotes && checkIfNeedQuotes(stringValue, attributeType) ? `'${stringValue}'` : stringValue;
4628
- };
4629
-
4630
4909
  /**
4631
4910
  * Подставляет плейсхолдеры уровней `%name.l{N}` значениями id соответствующего уровня
4632
4911
  * (формат `[id1,id2,...]` для оператора IN). Уровни, отсутствующие в значении (или пустые),
@@ -4674,10 +4953,10 @@ const isDevEnvironment = (() => {
4674
4953
  }
4675
4954
  })();
4676
4955
  /**
4677
- * Один раз на источник сообщает, что конфиг остался на устаревшей плоской форме.
4956
+ * Один раз на источник сообщает, что конфиг задаёт ось устаревшей плоской формой.
4678
4957
  *
4679
- * Без такого маркера момент «легаси закончилось» не наступает никогда, и временный слой
4680
- * совместимости становится постоянным.
4958
+ * Предупреждение показывает, какие конфиги ещё держатся на плоской форме, — без него её поддержку
4959
+ * нельзя убрать, не проверив все конфиги вручную.
4681
4960
  */
4682
4961
  const warnLegacyAxis = ({ dataSourceName, attributeName }) => {
4683
4962
  if (!isDevEnvironment)
@@ -4717,7 +4996,7 @@ const resolveAxis = (source) => {
4717
4996
  * Источник питает ось значений.
4718
4997
  *
4719
4998
  * Отсутствие `axis.type` означает `"y"`: конфиг без явного типа — самый частый случай,
4720
- * и раньше такой источник молча выпадал из графика.
4999
+ * и такой источник не должен выпадать из графика.
4721
5000
  */
4722
5001
  const isValueAxis = (source) => (resolveAxis(source).type ?? "y") === "y";
4723
5002
  /** Сторона оси; отсутствие `axis.side` означает `"left"`. */
@@ -4782,161 +5061,40 @@ const fetchQueryDescription = (api, { ds, query, parameters }) => {
4782
5061
  return request;
4783
5062
  };
4784
5063
 
4785
- const applyFiltersToCondition = ({ condition, name, defaultValue, filters, isSingle, isSetParams, configFilter, }) => {
4786
- const rawValue = filters[name] !== undefined ? filters[name].value : defaultValue;
4787
- // `features` живёт только в `parameters`: в условии значение прошло бы через
4788
- // `formatConditionValue` и выродилось в `[object Object]`. Плейсхолдер остаётся нетронутым —
4789
- // ровно как у любого незаполненного фильтра.
4790
- if (configFilter?.valueType === "features" || isFeaturesFilterValue(rawValue)) {
4791
- return condition;
4792
- }
4793
- if (isTreeFilterValue(rawValue, configFilter)) {
4794
- return applyTreeFilterToCondition(condition, name, rawValue, isSingle);
4795
- }
4796
- const hasFilter = filters[name] !== undefined && !!filters[name].value?.toString().length;
4797
- const filterName = `${FILTER_PREFIX}${name}`;
4798
- // Пока фильтр не применён, границы `%name.min` / `%name.max` берём из range-дефолта конфига
4799
- // (`defaultValue: [from, to]`) — иначе в условие ушёл бы весь массив вместо своей границы.
4800
- // Проверка плейсхолдеров обязательна: у array-фильтра массивный дефолт подставляется целиком.
4801
- const rangeDefault = !hasFilter &&
4802
- Array.isArray(defaultValue) &&
4803
- (condition.includes(`${filterName}.min`) || condition.includes(`${filterName}.max`))
4804
- ? defaultValue
4805
- : null;
4806
- const min = formatConditionValue({
4807
- value: hasFilter ? filters[name].min : rangeDefault?.[0] ?? null,
4808
- checkQuotes: !isSingle,
4809
- isSetParams,
4810
- });
4811
- const max = formatConditionValue({
4812
- value: hasFilter ? filters[name].max : rangeDefault?.[1] ?? null,
4813
- checkQuotes: !isSingle,
4814
- isSetParams,
4815
- });
4816
- const value = formatConditionValue({
4817
- value: hasFilter ? filters[name].value : filters[name] === undefined ? defaultValue : "",
4818
- defaultValue,
4819
- checkQuotes: !isSingle,
4820
- isSetParams,
4821
- });
4822
- const hasMin = min !== "" && min !== null && min !== undefined;
4823
- const hasMax = max !== "" && max !== null && max !== undefined;
4824
- let result = condition;
4825
- if (hasMin || hasMax) {
4826
- if (hasMin) {
4827
- result = result.replace(new RegExp(`${filterName}\\.min`), min);
4828
- }
4829
- if (hasMax) {
4830
- result = result.replace(new RegExp(`${filterName}\\.max`), max);
4831
- }
4832
- }
4833
- else if (value) {
4834
- result = result.replace(new RegExp(filterName), value);
4835
- }
4836
- return isSingle && isNumeric(result) ? Number(result) : result;
4837
- };
4838
- const applyVarsToCondition = ({ section, configFilters, filters, attributes, layerParams, eqlParameters, geometry, extent, zoomLevel, projectName, projectAlias, isSetParams, }) => {
5064
+ const toSources = ({ configFilters, filters, dataSources, dashboard, attributes, geometry, extent, zoomLevel, projectName, projectAlias, }) => ({
5065
+ widget: { selectedFilters: filters, configFilters, dataSources },
5066
+ dashboard,
5067
+ attributes,
5068
+ geometry,
5069
+ extent,
5070
+ zoomLevel,
5071
+ projectName,
5072
+ projectAlias,
5073
+ });
5074
+ /** Подстановки в секции условия (строке или массиве секций) — без разбора set-параметров. */
5075
+ const applyVarsToCondition = ({ section, isSetParams, ...params }) => {
4839
5076
  if (!section?.length)
4840
5077
  return [];
4841
- const isSingle = typeof section === "string";
4842
- const result = isSingle ? [section] : [...section];
4843
- result.forEach((_, index) => {
4844
- if (geometry && !geometry.includes("()") && geometry.endsWith(")")) {
4845
- result[index] = result[index].replace(new RegExp("%geometry"), `'${geometry}'`);
4846
- }
4847
- // Системные фильтры вида карты подставляются ДО цикла по `configFilters`: одноимённый
4848
- // пользовательский фильтр не должен перехватывать `%extent` / `%zoom`. Lookahead `(?![\w.])`
4849
- // оставляет фильтрам составные формы (`%zoomLevel`, `%extent_id`, `%zoom.min`) — та же
4850
- // граница, что уже разводит `%name` и `%name2.l1` в `getUpdatingDataSources`.
4851
- if (extent) {
4852
- result[index] = result[index].replace(new RegExp(`${FILTER_PREFIX}${EXTENT_FILTER_NAME}(?![\\w.])`, "g"), `'${extent}'`);
4853
- }
4854
- // Зум — число, кавычки не ставим.
4855
- if (!isNil(zoomLevel)) {
4856
- result[index] = result[index].replace(new RegExp(`${FILTER_PREFIX}${ZOOM_FILTER_NAME}(?![\\w.])`, "g"), String(zoomLevel));
4857
- }
4858
- // Текущий проект — строковая подстановка, поэтому в кавычках, как `%geometry`. Свойства
4859
- // заменяются раньше голого плейсхолдера, а граница `(?![\w.])` оставляет фильтрам составные
4860
- // формы (`%project_id`) и не трогает чужое свойство (`%project.foo`).
4861
- PROJECT_PROPS.forEach(prop => {
4862
- const projectValue = getProjectValue({ prop, projectName, projectAlias });
4863
- if (isNil(projectValue))
4864
- return;
4865
- result[index] = result[index].replace(new RegExp(`${FILTER_PREFIX}${PROJECT_FILTER_NAME}${prop ? `\\.${prop}` : ""}(?![\\w.])`, "g"), `'${projectValue}'`);
4866
- });
4867
- if (configFilters?.length) {
4868
- configFilters.forEach(filter => {
4869
- result[index] = applyFiltersToCondition({
4870
- filters,
4871
- condition: result[index],
4872
- name: filter.name,
4873
- defaultValue: filter.defaultValue,
4874
- configFilter: filter,
4875
- isSetParams,
4876
- isSingle: isSingle && !Array.isArray(filter.defaultValue),
4877
- }).toString();
4878
- });
4879
- }
4880
- if (attributes?.length) {
4881
- attributes.forEach(({ attributeName, value, type: attributeType }) => {
4882
- result[index] = result[index].replace(new RegExp(`{${attributeName}}`), formatConditionValue({ value: value?.toString(), attributeType, checkQuotes: !isSingle }));
4883
- });
4884
- }
4885
- if (eqlParameters && layerParams) {
4886
- Object.keys(eqlParameters).forEach(param => {
4887
- result[index] = result[index].replace(new RegExp(`\\$\\(${param}=`), "***");
4888
- result[index] = result[index].replace(new RegExp(param), formatConditionValue({ value: layerParams[param], checkQuotes: !isSingle }));
4889
- result[index] = result[index].replace(new RegExp("\\*\\*\\*"), `$(${param}=`);
4890
- });
4891
- }
4892
- });
4893
- return isSingle ? result?.[0] : result;
4894
- };
4895
- const formatSingleCondition = (condition, { configFilters, filters, attributes, eqlParameters, layerParams, geometry, extent, zoomLevel, projectName, projectAlias, }) => {
4896
- const setParams = condition.match(new RegExp("\\$\\([^)]+\\)", "g"));
4897
- const setParamsSection = applyVarsToCondition({
4898
- section: setParams,
4899
- configFilters,
4900
- filters,
4901
- attributes,
4902
- eqlParameters,
4903
- layerParams,
4904
- geometry,
4905
- extent,
4906
- zoomLevel,
4907
- projectName,
4908
- projectAlias,
4909
- isSetParams: true,
4910
- });
4911
- const splitter = " AND ";
4912
- const setParamsLength = setParams?.join("").length || 0;
4913
- const conditionSection = applyVarsToCondition({
4914
- section: condition.substring(setParamsLength).split(splitter),
4915
- configFilters,
4916
- filters,
4917
- attributes,
4918
- eqlParameters,
4919
- layerParams,
4920
- geometry,
4921
- extent,
4922
- zoomLevel,
4923
- projectName,
4924
- projectAlias,
4925
- });
4926
- return setParamsSection?.length && conditionSection.length
4927
- ? [setParamsSection.join(""), conditionSection.join(splitter)].join(" ")
4928
- : setParamsSection?.length
4929
- ? setParamsSection.join("")
4930
- : conditionSection.join(splitter);
5078
+ const sources = toSources(params);
5079
+ const { eqlParameters, layerParams } = params;
5080
+ const format = (item) => formatConditionSection(item, sources, { eqlParameters, layerParams, isSetParams });
5081
+ return (typeof section === "string" ? format(section) : section.map(format));
4931
5082
  };
5083
+ /**
5084
+ * Условие слоя с подстановками строк конфига: фильтры (`%filter`, `$dashboard:filter`), атрибуты
5085
+ * объекта, системные значения карты и проекта, eql-параметры слоя. Пустое значение фильтра
5086
+ * оставляет плейсхолдер нетронутым.
5087
+ */
4932
5088
  const formatDataSourceCondition = ({ condition, ...rest }) => {
5089
+ const sources = toSources(rest);
5090
+ const eql = { eqlParameters: rest.eqlParameters, layerParams: rest.layerParams };
4933
5091
  if (Array.isArray(condition)) {
4934
- return condition.map(item => formatSingleCondition(item, rest));
5092
+ return condition.map(item => formatConditionString(item, sources, eql));
4935
5093
  }
4936
5094
  if (!condition) {
4937
5095
  return "";
4938
5096
  }
4939
- return formatSingleCondition(condition, rest);
5097
+ return formatConditionString(condition, sources, eql);
4940
5098
  };
4941
5099
 
4942
5100
  const DashboardChipsContainer = styled(Flex).withConfig({ displayName: "DashboardChipsContainer", componentId: "sc-pzlzu7" }) `
@@ -5107,10 +5265,9 @@ const noMarginMixin = css `
5107
5265
  /**
5108
5266
  * Обёртка значения элемента.
5109
5267
  *
5110
- * Контейнеры, владеющие своим корнем (`ROOT_OWNING_TEMPLATES`), сюда больше не попадают —
5111
- * обёртка дублировала их `data-id`, `data-templatename` и `style` вторым узлом DOM.
5112
- * `$sizeTransparent` поэтому работает только на контейнерах, которые ещё не переведены
5113
- * на `useContainerRoot`.
5268
+ * Контейнеры, владеющие своим корнем (`ROOT_OWNING_TEMPLATES`), сюда не попадают — обёртка
5269
+ * продублировала бы их `data-id`, `data-templatename` и `style` вторым узлом DOM.
5270
+ * `$sizeTransparent` поэтому работает только на контейнерах без `useContainerRoot`.
5114
5271
  */
5115
5272
  const ElementValueWrapper = styled.div.withConfig({ displayName: "ElementValueWrapper", componentId: "sc-x3ra9l" }) `
5116
5273
  transition: background-color ${transition.toggle};
@@ -5139,8 +5296,8 @@ const ElementValueWrapper = styled.div.withConfig({ displayName: "ElementValueWr
5139
5296
  * а доля из `options.width` достаётся только телу. Поэтому `id`, `data-templatename`, авторский
5140
5297
  * `style` и `$sizeCss` живут здесь, а не на теле — см. `useContainerRoot`.
5141
5298
  *
5142
- * Всегда flex-колонка: заголовок и тело раньше были flex-элементами родительского `Container`,
5143
- * и блочная обёртка вернула бы схлопывание вертикальных отступов между ними.
5299
+ * Всегда flex-колонка: в блочной обёртке вертикальные отступы между заголовком и телом
5300
+ * схлопывались бы.
5144
5301
  */
5145
5302
  const ContainerRoot = styled(Flex).withConfig({ displayName: "ContainerRoot", componentId: "sc-1m2pft5" }) `
5146
5303
  flex-direction: column;
@@ -5608,6 +5765,34 @@ const getChartFilterName = (relatedDataSources) => {
5608
5765
  return axes?.[0]?.filterName;
5609
5766
  };
5610
5767
 
5768
+ const isValueChanged = (value, prevValue, configFilter) => {
5769
+ const isObjectValue = isTreeFilterValue(value, configFilter) ||
5770
+ isTreeFilterValue(prevValue, configFilter) ||
5771
+ isFeaturesFilterValue(value) ||
5772
+ isFeaturesFilterValue(prevValue);
5773
+ if (isObjectValue) {
5774
+ return JSON.stringify(value) !== JSON.stringify(prevValue);
5775
+ }
5776
+ if (Array.isArray(value)) {
5777
+ return (value.length !== prevValue?.length ||
5778
+ value.some((item, index) => item !== prevValue?.[index]));
5779
+ }
5780
+ return value !== prevValue;
5781
+ };
5782
+ /** Имена фильтров, чьё выбранное значение изменилось с прошлого снимка. */
5783
+ const getChangedFilterNames = (filters, prevFilters, configFilters) => filters
5784
+ ? Object.keys(filters).filter(key => isValueChanged(filters[key].value, prevFilters[key]?.value, getConfigFilter(key, configFilters)))
5785
+ : [];
5786
+ /**
5787
+ * Имена фильтров, чей дефолт изменился (`defaultValue: "$dashboard:year"` при смене фильтра дашборда),
5788
+ * пока пользователь не выбрал своё значение, — для потребителей это то же, что смена значения.
5789
+ */
5790
+ const getChangedDefaultNames = (configFilters, prevConfigFilters, filters) => (configFilters ?? [])
5791
+ .filter(({ name, defaultValue }) => filters?.[name] === undefined &&
5792
+ prevConfigFilters !== undefined &&
5793
+ !isEqual(defaultValue, getConfigFilter(name, prevConfigFilters)?.defaultValue))
5794
+ .map(({ name }) => name);
5795
+
5611
5796
  function getValueIndex(items, attributes) {
5612
5797
  return items?.findIndex(({ name }) => name.toString() === attributes.value?.toString());
5613
5798
  }
@@ -6252,29 +6437,227 @@ const useAutoCompleteControl = (items) => {
6252
6437
  .map(item => ({ text: String(item), value: String(item) })), [items]);
6253
6438
  const onChange = useCallback((newValue) => setValue(newValue), []);
6254
6439
  return {
6255
- value,
6256
- setValue,
6257
- onChange,
6258
- options: staticOptions.length ? staticOptions : options,
6259
- setOptions,
6440
+ value,
6441
+ setValue,
6442
+ onChange,
6443
+ options: staticOptions.length ? staticOptions : options,
6444
+ setOptions,
6445
+ };
6446
+ };
6447
+
6448
+ /**
6449
+ * Пропсы хоста фонового слоя для корней, которые не берут пропсы из `useWrapperSize`
6450
+ * (ставят `id`/`style` руками — `Title`, `Icon`, `Tabs`, `AddFeature`, `ExportPdf`, …).
6451
+ *
6452
+ * Контейнеры на `useContainerRoot` этот хук напрямую не зовут: `useWrapperSize` вызывает его
6453
+ * сам и кладёт результат в `root` — так признаки хоста считаются в одном месте.
6454
+ */
6455
+ const useBgImageHost = (elementConfig) => {
6456
+ const hasBgImage = useMemo(() => hasContainerBgImage(elementConfig), [elementConfig]);
6457
+ const innerPadding = !!elementConfig?.options?.innerPadding;
6458
+ return useMemo(() => ({ $hasBgImage: hasBgImage, $innerPadding: innerPadding }), [hasBgImage, innerPadding]);
6459
+ };
6460
+
6461
+ const getPagesFromConfig = (config) => config?.children?.[0]?.children;
6462
+
6463
+ /** Фильтры страницы `pageIndex` (с 1) вместе с общими фильтрами конфига; общие перекрывают одноимённые. */
6464
+ const getPageFilters = (config, pageIndex) => {
6465
+ const page = getPagesFromConfig(config)?.[pageIndex - 1];
6466
+ return [
6467
+ ...(page?.filters?.filter(({ name }) => !config?.filters?.some(item => item.name === name)) || []),
6468
+ ...(config?.filters || []),
6469
+ ];
6470
+ };
6471
+
6472
+ /**
6473
+ * Generic-параметр `T` сейчас идёт как намерение (под какой виджет). Дальнейшее сужение
6474
+ * `config` под конкретный виджет — отдельная задача.
6475
+ */
6476
+ const useWidgetConfig = (type = WidgetType.Dashboard) => {
6477
+ const { config: configProp, containerIds, projectInfo, layerInfo, isEditing } = useWidgetContext(type);
6478
+ const config = useMemo(() => {
6479
+ if (configProp) {
6480
+ return configProp;
6481
+ }
6482
+ if (isEditing) {
6483
+ return type === WidgetType.Dashboard
6484
+ ? (projectInfo?.content?.editConfiguration || {})
6485
+ : (layerInfo?.configuration?.editConfiguration || {});
6486
+ }
6487
+ const currentConfig = type === WidgetType.Dashboard
6488
+ ? (projectInfo?.content?.dashboardConfiguration || {})
6489
+ : (layerInfo?.configuration?.cardConfiguration || {});
6490
+ if (containerIds?.length) {
6491
+ const elements = containerIds.map(id => returnFound(currentConfig, { id })).filter(Boolean);
6492
+ if (!elements.length)
6493
+ return currentConfig;
6494
+ const newConfig = JSON.parse(JSON.stringify(currentConfig));
6495
+ const pages = getPagesFromConfig(newConfig);
6496
+ if (!pages?.[0]?.children)
6497
+ return currentConfig;
6498
+ pages[0].children = elements;
6499
+ return newConfig;
6500
+ }
6501
+ return currentConfig;
6502
+ }, [configProp, containerIds, layerInfo?.configuration?.cardConfiguration, projectInfo?.content?.dashboardConfiguration, type, isEditing]);
6503
+ const pages = useMemo(() => (getPagesFromConfig(config) || []), [config]);
6504
+ return {
6505
+ config,
6506
+ pages,
6507
+ };
6508
+ };
6509
+
6510
+ /**
6511
+ * Фильтры ближайшего дашборда для `$dashboard:` в строках конфига виджета `type`. На самом
6512
+ * дашборде стор не нужен (`undefined`): `$dashboard:name` там равен `%name`.
6513
+ *
6514
+ * Страница дашборда собирается напрямую из конфига, а не через `useWidgetPage`: тот сам зовёт этот хук.
6515
+ */
6516
+ const useDashboardFilterStore = (type) => {
6517
+ const { filters, dataSources, pageIndex } = useWidgetContext(WidgetType.Dashboard);
6518
+ const { config } = useWidgetConfig(WidgetType.Dashboard);
6519
+ const isDashboard = type === WidgetType.Dashboard;
6520
+ const configFilters = useMemo(() => (isDashboard ? undefined : getPageFilters(config, pageIndex)), [config, isDashboard, pageIndex]);
6521
+ return useMemo(() => (isDashboard ? undefined : { selectedFilters: filters, configFilters, dataSources }), [configFilters, dataSources, filters, isDashboard]);
6522
+ };
6523
+
6524
+ /**
6525
+ * Generic-параметр `T` — намерение под виджет. Дальнейшая параметризация — отдельная задача.
6526
+ *
6527
+ * Дефолты фильтров не-дашборда (карточки) с `$dashboard:` приходят уже разрезолвленными из фильтров
6528
+ * дашборда. Страница дашборда отдаётся как есть: её `currentPage` редактор пишет обратно в конфиг.
6529
+ */
6530
+ const useWidgetPage = (type = WidgetType.Dashboard) => {
6531
+ const { t } = useGlobalContext();
6532
+ const { pageIndex, changePage, projectInfo, updateProject } = useWidgetContext(type);
6533
+ const { config } = useWidgetConfig(type);
6534
+ const dashboardFilterStore = useDashboardFilterStore(type);
6535
+ const pageFilters = useMemo(() => getPageFilters(config, pageIndex), [config, pageIndex]);
6536
+ // Стор дашборда меняется на каждый его фильтр: без ссылок на `$dashboard:` страница от него не зависит.
6537
+ const defaultsStore = useMemo(() => (pageFilters.some(({ defaultValue }) => isDashboardReference(defaultValue)) ? dashboardFilterStore : undefined), [dashboardFilterStore, pageFilters]);
6538
+ const resolvedFilters = useMemo(() => resolveFilterDefaults(pageFilters, defaultsStore), [defaultsStore, pageFilters]);
6539
+ const filtersRef = useRef(resolvedFilters);
6540
+ // Стор дашборда пересобирается и на загрузку его датасорсов — страницу меняют только новые дефолты.
6541
+ if (!isEqual(filtersRef.current, resolvedFilters)) {
6542
+ filtersRef.current = resolvedFilters;
6543
+ }
6544
+ const filters = filtersRef.current;
6545
+ const currentPage = useMemo(() => {
6546
+ const page = getPagesFromConfig(config)?.[pageIndex - 1];
6547
+ return {
6548
+ ...(page || {}),
6549
+ header: page?.header || config.header,
6550
+ dataSources: [
6551
+ ...(page?.dataSources?.filter(({ name }) => !config.dataSources?.some(item => item.name === name)) || []),
6552
+ ...(config.dataSources || []),
6553
+ ],
6554
+ filters,
6555
+ layers: page?.layers?.map((layer) => ({
6556
+ ...layer,
6557
+ isVisible: layer.isVisible ?? true,
6558
+ })) || [],
6559
+ };
6560
+ }, [config, pageIndex, filters]);
6561
+ const updateConfigPage = useCallback((newConfig) => {
6562
+ const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
6563
+ const pages = getPagesFromProjectInfo(newProjectInfo);
6564
+ if (!pages?.[pageIndex - 1])
6565
+ return;
6566
+ pages[pageIndex - 1] = { ...pages[pageIndex - 1], ...newConfig };
6567
+ updateProject(newProjectInfo);
6568
+ }, [projectInfo, pageIndex, updateProject]);
6569
+ const addConfigPage = useCallback(() => {
6570
+ const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
6571
+ const pages = getPagesFromProjectInfo(newProjectInfo);
6572
+ if (!pages)
6573
+ return;
6574
+ pages.push(createConfigPage({ pages, defaultTitle: t("page", { ns: "dashboard", defaultValue: "Страница" }) }));
6575
+ updateProject(newProjectInfo);
6576
+ changePage(pages.length);
6577
+ }, [changePage, projectInfo, t, updateProject]);
6578
+ const deleteConfigPage = useCallback((index) => {
6579
+ const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
6580
+ const pages = getPagesFromProjectInfo(newProjectInfo);
6581
+ if (!pages?.[index] || pages.length === 1)
6582
+ return;
6583
+ pages.splice(index, 1);
6584
+ updateProject(newProjectInfo);
6585
+ if (!pages[pageIndex]) {
6586
+ changePage(pageIndex > 1 ? pageIndex - 1 : 1);
6587
+ }
6588
+ }, [changePage, pageIndex, projectInfo, updateProject]);
6589
+ const updateConfigLayer = useCallback((name, newProps) => {
6590
+ const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
6591
+ const pages = getPagesFromProjectInfo(newProjectInfo);
6592
+ const page = pages?.[pageIndex - 1];
6593
+ const index = page?.layers?.findIndex(layer => layer.name === name) ?? -1;
6594
+ if (index === -1)
6595
+ return;
6596
+ page.layers[index] = { ...page.layers[index], ...newProps };
6597
+ updateProject(newProjectInfo);
6598
+ }, [projectInfo, pageIndex, updateProject]);
6599
+ const updateConfigLayers = useCallback((props) => {
6600
+ const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
6601
+ const pages = getPagesFromProjectInfo(newProjectInfo);
6602
+ const page = pages?.[pageIndex - 1];
6603
+ if (!page?.layers?.length)
6604
+ return;
6605
+ page.layers = page.layers.map(layer => ({
6606
+ ...layer,
6607
+ ...(props[layer.name] || {}),
6608
+ }));
6609
+ updateProject(newProjectInfo);
6610
+ }, [projectInfo, pageIndex, updateProject]);
6611
+ return {
6612
+ pageIndex,
6613
+ currentPage,
6614
+ changePage,
6615
+ updateConfigPage,
6616
+ addConfigPage,
6617
+ deleteConfigPage,
6618
+ updateConfigLayer,
6619
+ updateConfigLayers,
6260
6620
  };
6261
6621
  };
6262
6622
 
6263
6623
  /**
6264
- * Пропсы хоста фонового слоя для корней, которые не берут пропсы из `useWrapperSize`
6265
- * (ставят `id`/`style` руками — `Title`, `Icon`, `Tabs`, `AddFeature`, `ExportPdf`, …).
6266
- *
6267
- * Контейнеры на `useContainerRoot` этот хук напрямую не зовут: `useWrapperSize` вызывает его
6268
- * сам и кладёт результат в `root` — так признаки хоста считаются в одном месте.
6624
+ * Все источники подстановок строк конфига для виджета `type`: его фильтры (`%`), фильтры и
6625
+ * датасорсы дашборда (`$dashboard:`), атрибуты объекта, вид карты и проект.
6626
+ * Атрибуты — объекта виджета; строки `DataSource` подменяют их своими при рендере.
6269
6627
  */
6270
- const useBgImageHost = (elementConfig) => {
6271
- const hasBgImage = useMemo(() => hasContainerBgImage(elementConfig), [elementConfig]);
6272
- const innerPadding = !!elementConfig?.options?.innerPadding;
6273
- return useMemo(() => ({ $hasBgImage: hasBgImage, $innerPadding: innerPadding }), [hasBgImage, innerPadding]);
6628
+ const useConfigStringSources = (type) => {
6629
+ const { ewktGeometry, ewktExtent, zoomLevel, projectName, projectAlias } = useGlobalContext();
6630
+ const { filters, dataSources, attributes, layerInfo } = useWidgetContext(type);
6631
+ const { currentPage } = useWidgetPage(type);
6632
+ const dashboard = useDashboardFilterStore(type);
6633
+ return useMemo(() => ({
6634
+ widget: { selectedFilters: filters, configFilters: currentPage?.filters, dataSources },
6635
+ dashboard,
6636
+ attributes,
6637
+ layerInfo,
6638
+ geometry: ewktGeometry,
6639
+ extent: ewktExtent,
6640
+ zoomLevel,
6641
+ projectName,
6642
+ projectAlias,
6643
+ }), [
6644
+ attributes,
6645
+ currentPage?.filters,
6646
+ dashboard,
6647
+ dataSources,
6648
+ ewktExtent,
6649
+ ewktGeometry,
6650
+ filters,
6651
+ layerInfo,
6652
+ projectAlias,
6653
+ projectName,
6654
+ zoomLevel,
6655
+ ]);
6274
6656
  };
6275
6657
 
6276
6658
  const useRenderContainerItem = (type, renderElement) => {
6277
6659
  const { config, layerInfo, selectedTabId, attributes } = useWidgetContext(type);
6660
+ const configStringSources = useConfigStringSources(type);
6278
6661
  return useCallback((elementConfig, attribute) => {
6279
6662
  const { id, options, style, children } = elementConfig || {};
6280
6663
  const { hideEmpty, innerTemplateStyle } = options || {};
@@ -6307,6 +6690,7 @@ const useRenderContainerItem = (type, renderElement) => {
6307
6690
  attributes,
6308
6691
  layerInfo,
6309
6692
  type,
6693
+ configStringSources,
6310
6694
  })
6311
6695
  : renderElement;
6312
6696
  const value = render({ id: "value" });
@@ -6319,7 +6703,7 @@ const useRenderContainerItem = (type, renderElement) => {
6319
6703
  hasUnits,
6320
6704
  render,
6321
6705
  };
6322
- }, [config, layerInfo, selectedTabId, attributes, type, renderElement]);
6706
+ }, [config, layerInfo, selectedTabId, attributes, type, renderElement, configStringSources]);
6323
6707
  };
6324
6708
 
6325
6709
  const customModes = MapboxDraw.modes;
@@ -7069,8 +7453,8 @@ const useContainerAttributes = ({ elementConfig, type, renderElement }) => {
7069
7453
  * Размеры уходят styled-пропом (класс), а не inline-стилем, поэтому перебиваются снаружи
7070
7454
  * без `!important`. Авторский `style` остаётся inline — у него приоритет по замыслу автора конфига.
7071
7455
  *
7072
- * `data-id` и `$noMargin` раньше жили на внешней обёртке `ElementValueWrapper`; она вешала их
7073
- * на второй узел поверх этого корня, дублируя стили, и для таких контейнеров больше не создаётся
7456
+ * `data-id` и `$noMargin` живут на самом корне: внешняя обёртка `ElementValueWrapper` повесила бы
7457
+ * их на второй узел поверх него, дублируя стили, поэтому для таких контейнеров она не создаётся
7074
7458
  * (см. `ROOT_OWNING_TEMPLATES`).
7075
7459
  *
7076
7460
  * Пропсы хоста фонового слоя (`$hasBgImage`, `$innerPadding`) подмешиваются здесь, а не в каждом
@@ -7483,132 +7867,6 @@ const getModalsDataSources = (config, modalIds) => uniqBy((config?.modals ?? [])
7483
7867
  .filter(({ id }) => !modalIds || modalIds.includes(id))
7484
7868
  .flatMap(({ dataSources }) => dataSources ?? []), "name");
7485
7869
 
7486
- /**
7487
- * Generic-параметр `T` сейчас идёт как намерение (под какой виджет). Дальнейшее сужение
7488
- * `config` под конкретный виджет — отдельная задача.
7489
- */
7490
- const useWidgetConfig = (type = WidgetType.Dashboard) => {
7491
- const { config: configProp, containerIds, projectInfo, layerInfo, isEditing } = useWidgetContext(type);
7492
- const config = useMemo(() => {
7493
- if (configProp) {
7494
- return configProp;
7495
- }
7496
- if (isEditing) {
7497
- return type === WidgetType.Dashboard
7498
- ? (projectInfo?.content?.editConfiguration || {})
7499
- : (layerInfo?.configuration?.editConfiguration || {});
7500
- }
7501
- const currentConfig = type === WidgetType.Dashboard
7502
- ? (projectInfo?.content?.dashboardConfiguration || {})
7503
- : (layerInfo?.configuration?.cardConfiguration || {});
7504
- if (containerIds?.length) {
7505
- const elements = containerIds.map(id => returnFound(currentConfig, { id })).filter(Boolean);
7506
- if (!elements.length)
7507
- return currentConfig;
7508
- const newConfig = JSON.parse(JSON.stringify(currentConfig));
7509
- const pages = getPagesFromConfig(newConfig);
7510
- if (!pages?.[0]?.children)
7511
- return currentConfig;
7512
- pages[0].children = elements;
7513
- return newConfig;
7514
- }
7515
- return currentConfig;
7516
- }, [configProp, containerIds, layerInfo?.configuration?.cardConfiguration, projectInfo?.content?.dashboardConfiguration, type, isEditing]);
7517
- const pages = useMemo(() => (getPagesFromConfig(config) || []), [config]);
7518
- return {
7519
- config,
7520
- pages,
7521
- };
7522
- };
7523
-
7524
- /**
7525
- * Generic-параметр `T` — намерение под виджет. Дальнейшая параметризация — отдельная задача.
7526
- */
7527
- const useWidgetPage = (type = WidgetType.Dashboard) => {
7528
- const { t } = useGlobalContext();
7529
- const { pageIndex, changePage, projectInfo, updateProject } = useWidgetContext(type);
7530
- const { config } = useWidgetConfig(type);
7531
- const currentPage = useMemo(() => {
7532
- const page = getPagesFromConfig(config)?.[pageIndex - 1];
7533
- return {
7534
- ...(page || {}),
7535
- header: page?.header || config.header,
7536
- dataSources: [
7537
- ...(page?.dataSources?.filter(({ name }) => !config.dataSources?.some(item => item.name === name)) || []),
7538
- ...(config.dataSources || [])
7539
- ],
7540
- filters: [
7541
- ...(page?.filters?.filter(({ name }) => !config.filters?.some(item => item.name === name)) || []),
7542
- ...(config.filters || [])
7543
- ],
7544
- layers: page?.layers?.map((layer) => ({
7545
- ...layer,
7546
- isVisible: layer.isVisible ?? true
7547
- })) || []
7548
- };
7549
- }, [config, pageIndex]);
7550
- const updateConfigPage = useCallback((newConfig) => {
7551
- const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
7552
- const pages = getPagesFromProjectInfo(newProjectInfo);
7553
- if (!pages?.[pageIndex - 1])
7554
- return;
7555
- pages[pageIndex - 1] = { ...pages[pageIndex - 1], ...newConfig };
7556
- updateProject(newProjectInfo);
7557
- }, [projectInfo, pageIndex, updateProject]);
7558
- const addConfigPage = useCallback(() => {
7559
- const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
7560
- const pages = getPagesFromProjectInfo(newProjectInfo);
7561
- if (!pages)
7562
- return;
7563
- pages.push(createConfigPage({ pages, defaultTitle: t("page", { ns: "dashboard", defaultValue: "Страница" }) }));
7564
- updateProject(newProjectInfo);
7565
- changePage(pages.length);
7566
- }, [changePage, projectInfo, t, updateProject]);
7567
- const deleteConfigPage = useCallback((index) => {
7568
- const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
7569
- const pages = getPagesFromProjectInfo(newProjectInfo);
7570
- if (!pages?.[index] || pages.length === 1)
7571
- return;
7572
- pages.splice(index, 1);
7573
- updateProject(newProjectInfo);
7574
- if (!pages[pageIndex]) {
7575
- changePage(pageIndex > 1 ? pageIndex - 1 : 1);
7576
- }
7577
- }, [changePage, pageIndex, projectInfo, updateProject]);
7578
- const updateConfigLayer = useCallback((name, newProps) => {
7579
- const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
7580
- const pages = getPagesFromProjectInfo(newProjectInfo);
7581
- const page = pages?.[pageIndex - 1];
7582
- const index = page?.layers?.findIndex(layer => layer.name === name) ?? -1;
7583
- if (index === -1)
7584
- return;
7585
- page.layers[index] = { ...page.layers[index], ...newProps };
7586
- updateProject(newProjectInfo);
7587
- }, [projectInfo, pageIndex, updateProject]);
7588
- const updateConfigLayers = useCallback((props) => {
7589
- const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
7590
- const pages = getPagesFromProjectInfo(newProjectInfo);
7591
- const page = pages?.[pageIndex - 1];
7592
- if (!page?.layers?.length)
7593
- return;
7594
- page.layers = page.layers.map(layer => ({
7595
- ...layer,
7596
- ...(props[layer.name] || {}),
7597
- }));
7598
- updateProject(newProjectInfo);
7599
- }, [projectInfo, pageIndex, updateProject]);
7600
- return {
7601
- pageIndex,
7602
- currentPage,
7603
- changePage,
7604
- updateConfigPage,
7605
- addConfigPage,
7606
- deleteConfigPage,
7607
- updateConfigLayer,
7608
- updateConfigLayers,
7609
- };
7610
- };
7611
-
7612
7870
  /**
7613
7871
  * Конфиги всех источников, доступных контейнерам виджета: страница с корнем и все модалки.
7614
7872
  * Одноимённый источник модалки перекрывается страничным. Только для поиска по имени — в конфиг
@@ -7709,6 +7967,7 @@ const useHeaderRender = (elementConfig, type = WidgetType.Dashboard) => {
7709
7967
  const { layerInfo, attributes, expandedContainers, selectedTabId, setSelectedTabId, } = useWidgetContext(type);
7710
7968
  const { config } = useWidgetConfig(type);
7711
7969
  const { pageIndex } = useWidgetPage(type);
7970
+ const configStringSources = useConfigStringSources(type);
7712
7971
  return useMemo(() => getRenderElement({
7713
7972
  config,
7714
7973
  elementConfig,
@@ -7718,8 +7977,10 @@ const useHeaderRender = (elementConfig, type = WidgetType.Dashboard) => {
7718
7977
  selectedTabId,
7719
7978
  setSelectedTabId,
7720
7979
  pageIndex,
7980
+ configStringSources,
7721
7981
  }), [
7722
7982
  config,
7983
+ configStringSources,
7723
7984
  elementConfig,
7724
7985
  attributes,
7725
7986
  layerInfo,
@@ -7777,9 +8038,11 @@ const useDataSourceLoading = (type) => {
7777
8038
  const useDataSources = ({ type: widgetType, config, attributes, filters, layerParams, eqlParameters, }) => {
7778
8039
  const { ewktGeometry, ewktExtent, zoomLevel, projectName, projectAlias, api } = useGlobalContext();
7779
8040
  const { dataSources, layerInfo } = useWidgetContext(widgetType);
7780
- const { dataSources: projectDataSources } = useWidgetContext(WidgetType.Dashboard);
8041
+ const dashboard = useDashboardFilterStore(widgetType ?? WidgetType.Dashboard);
7781
8042
  const { filters: configFilters, dataSources: configDataSources } = config || {};
7782
8043
  const prevFilters = useRef({});
8044
+ const prevDashboardFilters = useRef(dashboard?.selectedFilters ?? {});
8045
+ const prevConfigFilters = useRef(configFilters);
7783
8046
  const inFlightRequests = useRef(new Map());
7784
8047
  const getDataSourcePromises = useCallback(async ({ ds, query, parameters, layerName, limit, condition, url, resourceId, fileName, methodName, }, newFilters, offset = 0) => {
7785
8048
  // Дедуп in-flight по сигнатуре резолвленного запроса: если идентичный
@@ -7809,7 +8072,7 @@ const useDataSources = ({ type: widgetType, config, attributes, filters, layerPa
7809
8072
  projectAlias,
7810
8073
  layerInfo,
7811
8074
  dataSources,
7812
- projectDataSources,
8075
+ dashboard,
7813
8076
  });
7814
8077
  if (resourceId) {
7815
8078
  const newSignature = JSON.stringify(["python", resourceId, newParams, methodName, fileName]);
@@ -7880,6 +8143,8 @@ const useDataSources = ({ type: widgetType, config, attributes, filters, layerPa
7880
8143
  condition,
7881
8144
  configFilters,
7882
8145
  filters: selectedFilters,
8146
+ dataSources,
8147
+ dashboard,
7883
8148
  geometry: ewktGeometry,
7884
8149
  extent: ewktExtent,
7885
8150
  zoomLevel,
@@ -7914,50 +8179,34 @@ const useDataSources = ({ type: widgetType, config, attributes, filters, layerPa
7914
8179
  attributes,
7915
8180
  layerParams,
7916
8181
  layerInfo,
7917
- projectDataSources,
7918
8182
  eqlParameters,
8183
+ dashboard,
7919
8184
  ]);
8185
+ /**
8186
+ * Датасорсы, которые нужно перезапросить: их параметры ссылаются на изменившийся фильтр виджета
8187
+ * (`%name`, в том числе через дефолт `$dashboard:`) или дашборда (`$dashboard:name`).
8188
+ * Датасорсы слоя без `query` перезапрашиваются на любое изменение.
8189
+ */
7920
8190
  const getUpdatingDataSources = useCallback(() => {
7921
- const diffFilterNames = filters
7922
- ? Object.keys(filters).filter(key => {
7923
- const { value } = filters[key];
7924
- const prevValue = prevFilters.current[key]?.value;
7925
- const configFilter = getConfigFilter(key, configFilters);
7926
- const isObjectValue = isTreeFilterValue(value, configFilter) ||
7927
- isTreeFilterValue(prevValue, configFilter) ||
7928
- isFeaturesFilterValue(value) ||
7929
- isFeaturesFilterValue(prevValue);
7930
- if (isObjectValue) {
7931
- return JSON.stringify(value) !== JSON.stringify(prevValue);
7932
- }
7933
- if (Array.isArray(value)) {
7934
- return (value.length !== prevValue?.length ||
7935
- value.some((item, index) => item !== prevValue?.[index]));
7936
- }
7937
- return value !== prevValue;
7938
- })
7939
- : [];
7940
- if (!diffFilterNames.length)
7941
- return;
7942
- const filteredDataSources = configDataSources?.filter(({ parameters = {}, query }) => isNil(query) ||
7943
- diffFilterNames.some(diffFilterName => {
7944
- // Плейсхолдер параметра может ссылаться на фильтр целиком (`%name`)
7945
- // либо на его свойство — уровень tree-фильтра или границу range
7946
- // (`%name.lN`, `%name.min`, `%name.max`). Граница `.` обязательна,
7947
- // иначе `%name` ложно матчит `%name2.l1`.
7948
- const placeholder = `${FILTER_PREFIX}${diffFilterName}`;
7949
- return Object.keys(parameters).some(paramName => {
7950
- const paramValue = parameters[paramName];
7951
- return (typeof paramValue === "string" &&
7952
- (paramValue === placeholder || paramValue.startsWith(`${placeholder}.`)));
7953
- });
7954
- }));
7955
- if (!filteredDataSources?.length) {
8191
+ const filteredDataSources = selectUpdatingDataSources({
8192
+ configDataSources,
8193
+ filters,
8194
+ configFilters,
8195
+ dashboard,
8196
+ prev: {
8197
+ filters: prevFilters.current,
8198
+ configFilters: prevConfigFilters.current,
8199
+ dashboardFilters: prevDashboardFilters.current,
8200
+ },
8201
+ });
8202
+ if (!filteredDataSources) {
7956
8203
  return;
7957
8204
  }
7958
8205
  prevFilters.current = JSON.parse(JSON.stringify(filters));
8206
+ prevDashboardFilters.current = JSON.parse(JSON.stringify(dashboard?.selectedFilters ?? {}));
8207
+ prevConfigFilters.current = configFilters;
7959
8208
  return filteredDataSources;
7960
- }, [configDataSources, configFilters, filters]);
8209
+ }, [configDataSources, configFilters, dashboard, filters]);
7961
8210
  const getUpdatedDataSources = useCallback((responses, currentDataSources, otherDataSources) => {
7962
8211
  const newDataSources = JSON.parse(JSON.stringify([...currentDataSources, ...otherDataSources]));
7963
8212
  responses.forEach((response, index) => {
@@ -8299,8 +8548,9 @@ const useExportPdf = (id, margin = 20) => {
8299
8548
  const useSavePrototypeBuilder = () => {
8300
8549
  const { ewktGeometry } = useGlobalContext();
8301
8550
  const { layerInfo, attributes, dataSources, filters: selectedFilters, } = useWidgetContext(WidgetType.FeatureCard);
8302
- const { projectInfo, dataSources: projectDataSources } = useWidgetContext(WidgetType.Dashboard);
8551
+ const { projectInfo } = useWidgetContext(WidgetType.Dashboard);
8303
8552
  const { currentPage } = useWidgetPage(WidgetType.FeatureCard);
8553
+ const dashboard = useDashboardFilterStore(WidgetType.FeatureCard);
8304
8554
  return useCallback((hook, input) => {
8305
8555
  const resolvedParameters = applyQueryFilters({
8306
8556
  parameters: hook.parameters ?? {},
@@ -8310,7 +8560,7 @@ const useSavePrototypeBuilder = () => {
8310
8560
  attributes,
8311
8561
  layerInfo,
8312
8562
  dataSources,
8313
- projectDataSources,
8563
+ dashboard,
8314
8564
  });
8315
8565
  const scriptParameters = {
8316
8566
  projectName: projectInfo?.name,
@@ -8343,10 +8593,10 @@ const useSavePrototypeBuilder = () => {
8343
8593
  }, [
8344
8594
  attributes,
8345
8595
  currentPage?.filters,
8596
+ dashboard,
8346
8597
  dataSources,
8347
8598
  ewktGeometry,
8348
8599
  layerInfo,
8349
- projectDataSources,
8350
8600
  projectInfo?.name,
8351
8601
  selectedFilters,
8352
8602
  ]);
@@ -8568,6 +8818,7 @@ const useRenderElement = (type = WidgetType.Dashboard, elementConfig) => {
8568
8818
  const { selectedTabId, setSelectedTabId, expandedContainers, layerInfo, attributes } = useWidgetContext(type);
8569
8819
  const { config } = useWidgetConfig(type);
8570
8820
  const { pageIndex } = useWidgetPage(type);
8821
+ const configStringSources = useConfigStringSources(type);
8571
8822
  return useMemo(() => getRenderElement({
8572
8823
  t,
8573
8824
  type,
@@ -8579,9 +8830,11 @@ const useRenderElement = (type = WidgetType.Dashboard, elementConfig) => {
8579
8830
  pageIndex,
8580
8831
  selectedTabId,
8581
8832
  setSelectedTabId,
8833
+ configStringSources,
8582
8834
  }), [
8583
8835
  attributes,
8584
8836
  config,
8837
+ configStringSources,
8585
8838
  elementConfig,
8586
8839
  expandedContainers,
8587
8840
  layerInfo,
@@ -9085,9 +9338,8 @@ const LinkDialogContent = styled.div.withConfig({ displayName: "LinkDialogConten
9085
9338
  * Заглушка недоступного изображения.
9086
9339
  *
9087
9340
  * Масштаб значка считается от размера самого превью: `font-size` контейнера приравнен к нему, а
9088
- * бокс значка и его глиф заданы одной долей от этого размера. Раньше бокс был долей превью, а
9089
- * глиф — фиксированным: в списке, где превью всего `1.5rem`, глиф выходил втрое крупнее своего
9090
- * бокса и съезжал относительно квадрата.
9341
+ * бокс значка и его глиф заданы одной долей от этого размера. Фиксированный глиф в мелком превью
9342
+ * (`1.5rem` в списке) вышел бы крупнее своего бокса и съехал относительно квадрата.
9091
9343
  */
9092
9344
  const ImagePreviewError = styled.div.withConfig({ displayName: "ImagePreviewError", componentId: "sc-7cwrhm" }) `
9093
9345
  display: flex;
@@ -9659,6 +9911,7 @@ const DataSourceInnerContainer = memo(({ config, elementConfig, feature, maxValu
9659
9911
  valueElement?.attributeName,
9660
9912
  ]);
9661
9913
  const { hasAnyFilter, isFiltered, onFilter } = useWidgetFilters(type, filterName, data);
9914
+ const configStringSources = useConfigStringSources(type);
9662
9915
  const render = useMemo(() => getRenderElement({
9663
9916
  config,
9664
9917
  elementConfig,
@@ -9669,7 +9922,19 @@ const DataSourceInnerContainer = memo(({ config, elementConfig, feature, maxValu
9669
9922
  setSelectedTabId,
9670
9923
  pageIndex,
9671
9924
  type,
9672
- }), [config, elementConfig, attributes, layerInfo, expandedContainers, selectedTabId, setSelectedTabId, pageIndex, type]);
9925
+ configStringSources,
9926
+ }), [
9927
+ config,
9928
+ elementConfig,
9929
+ attributes,
9930
+ layerInfo,
9931
+ expandedContainers,
9932
+ selectedTabId,
9933
+ setSelectedTabId,
9934
+ pageIndex,
9935
+ type,
9936
+ configStringSources,
9937
+ ]);
9673
9938
  if (!InnerContainer) {
9674
9939
  return null;
9675
9940
  }
@@ -10022,8 +10287,8 @@ const TreeFilterContainer = styled.div.withConfig({ displayName: "TreeFilterCont
10022
10287
  }
10023
10288
  `;
10024
10289
  const TextFilterContainer = styled.div.withConfig({ displayName: "TextFilterContainer", componentId: "sc-1cbkxpd" }) `
10025
- /* Ширину задаёт контейнер фильтров. Раньше здесь стояли жёсткие 15.85rem, из-за чего
10026
- текстовый фильтр вставал уже соседних. */
10290
+ /* Ширину задаёт контейнер фильтров: своя фиксированная ширина сделала бы текстовый фильтр
10291
+ уже соседних. */
10027
10292
  width: 100%;
10028
10293
 
10029
10294
  ${MultiSelectContainer} {
@@ -11247,6 +11512,7 @@ const PagesContainer = memo(({ type = WidgetType.Dashboard, noBorders }) => {
11247
11512
  const { config } = useWidgetConfig(type);
11248
11513
  const { pageIndex, currentPage } = useWidgetPage(type);
11249
11514
  const { selectedTabId, setSelectedTabId, expandedContainers, attributes, onContainerChange } = useWidgetContext(type);
11515
+ const configStringSources = useConfigStringSources(type);
11250
11516
  const { options } = config || {};
11251
11517
  const { column, width } = options || {};
11252
11518
  const isColumn = column === undefined || column;
@@ -11264,12 +11530,13 @@ const PagesContainer = memo(({ type = WidgetType.Dashboard, noBorders }) => {
11264
11530
  selectedTabId,
11265
11531
  setSelectedTabId,
11266
11532
  onChange: onContainerChange,
11533
+ configStringSources,
11267
11534
  }), [
11268
11535
  attributes,
11269
11536
  config,
11537
+ configStringSources,
11270
11538
  currentPage,
11271
11539
  expandedContainers,
11272
- getRenderElement,
11273
11540
  onContainerChange,
11274
11541
  pageIndex,
11275
11542
  selectedTabId,
@@ -11369,8 +11636,8 @@ const TINTED_BACKGROUND_PERCENT = 6;
11369
11636
  /** Тонированный фон из цвета-источника; без цвета — `undefined`, чтобы уступить следующему в приоритете. */
11370
11637
  const tintedBackground = (color) => color ? transparentizeColor(color, TINTED_BACKGROUND_PERCENT) : undefined;
11371
11638
  /**
11372
- * Отступ между элементами плитки: заданный в конфиге `innerGap` (px) либо исторический хардкод,
11373
- * если опция не задана — так вид уже существующих дашбордов не меняется.
11639
+ * Отступ между элементами плитки: заданный в конфиге `innerGap` (px) либо собственный отступ
11640
+ * элемента по умолчанию, если опция не задана.
11374
11641
  */
11375
11642
  const innerGapOr = (fallback) => ({ $innerGap }) => ($innerGap == null ? fallback : `${$innerGap}px`);
11376
11643
  /**
@@ -11484,7 +11751,7 @@ const RoundedBackgroundContainerWrapper = styled(Flex).withConfig({ displayName:
11484
11751
  text-align: left;
11485
11752
  }
11486
11753
 
11487
- /* Зазор наружу даёт обёртка иконки, здесь — исторический отступ внутри её бокса. */
11754
+ /* Зазор наружу даёт обёртка иконки, здесь — отступ внутри её бокса. */
11488
11755
  span[kind] {
11489
11756
  margin-right: 0.5rem;
11490
11757
  }
@@ -11504,7 +11771,7 @@ const RoundedBackgroundContainerWrapper = styled(Flex).withConfig({ displayName:
11504
11771
 
11505
11772
  ${ContainerUnits} {
11506
11773
  margin-left: ${({ $inlineUnits, $innerGap }) => ($inlineUnits ? innerGapOr("0.25rem")({ $innerGap }) : 0)};
11507
- /* Единицы под значением: зазора исторически не было, поэтому дефолт — 0. */
11774
+ /* Единицы под значением: без innerGap зазора нет. */
11508
11775
  margin-top: ${({ $inlineUnits, $innerGap }) => (!$inlineUnits && $innerGap != null ? `${$innerGap}px` : 0)};
11509
11776
  }
11510
11777
 
@@ -11547,17 +11814,17 @@ const RoundedBackgroundContainer = memo(({ type, elementConfig, feature, renderE
11547
11814
  const color = useMemo(() => attributes?.find(({ attributeName }) => attributeName === colorAttribute)?.value || fontColor, [attributes, colorAttribute, fontColor]);
11548
11815
  /**
11549
11816
  * Цвет иконки — только источник фона: сам глиф красит ElementIcon/ElementSvg своим `fontColor`.
11550
- * Исторический `style.color` читается фолбэком — тем же порядком, что и в редакторе.
11817
+ * `style.color` читается фолбэком — тем же порядком, что и в редакторе.
11551
11818
  */
11552
11819
  const iconColor = useMemo(() => iconElement?.options?.fontColor || iconElement?.style?.color, [iconElement]);
11553
11820
  /**
11554
11821
  * Отступ иконки задаётся на её обёртке `ContainerIconWrapper`: сам элемент `icon` рендерится через
11555
11822
  * ElementIcon/ElementSvg со своими styled-компонентами, до него из стилей плитки не дотянуться.
11556
11823
  *
11557
- * В `big` иконка и текст стоят в строку — зазор горизонтальный (исторический 0.5rem).
11558
- * В колоночной плитке зазор вертикальный, и исторически его не было: `mb` появляется только
11824
+ * В `big` иконка и текст стоят в строку — зазор горизонтальный (по умолчанию 0.5rem).
11825
+ * В колоночной плитке зазор вертикальный и по умолчанию нулевой: `mb` появляется только
11559
11826
  * с заданным `innerGap`. `mr` там на вертикаль не влияет, но участвует в горизонтальном
11560
- * центрировании, поэтому остаётся прежним — иначе иконка сдвинулась бы у всех существующих плиток.
11827
+ * центрировании, поэтому от `innerGap` не зависит — иначе он сдвигал бы иконку вбок.
11561
11828
  */
11562
11829
  const renderIcon = useMemo(() => {
11563
11830
  if (!iconElement)
@@ -11603,6 +11870,7 @@ const SlideshowContainer = memo(({ config, elementConfig, type }) => {
11603
11870
  elementConfig,
11604
11871
  dataSources,
11605
11872
  });
11873
+ const configStringSources = useConfigStringSources(type);
11606
11874
  const { id, options } = elementConfig || {};
11607
11875
  const { expandable, expanded } = options || {};
11608
11876
  const isVisible = isVisibleContainer(id, expandable, expanded, expandedContainers);
@@ -11617,9 +11885,11 @@ const SlideshowContainer = memo(({ config, elementConfig, type }) => {
11617
11885
  setSelectedTabId,
11618
11886
  pageIndex,
11619
11887
  type,
11888
+ configStringSources,
11620
11889
  }), [
11621
11890
  getRenderElement,
11622
11891
  config,
11892
+ configStringSources,
11623
11893
  elementConfig,
11624
11894
  attributes,
11625
11895
  layerInfo,
@@ -11859,7 +12129,7 @@ const useStructuredDataDraft = ({ schema, dataSource, hasDataSource, filterValue
11859
12129
  }, []);
11860
12130
  const reset = useCallback(() => setRows(baseline.current), []);
11861
12131
  const commitSaved = useCallback((next) => {
11862
- // Сохранённое состояние — наше: значение фильтра теперь пишем мы, и вернувшееся из него
12132
+ // Сохранённое состояние — наше: значение фильтра записали мы, и вернувшееся из него
11863
12133
  // обновление не должно пересоздавать ключи уже показанных строк.
11864
12134
  hydrated.current = true;
11865
12135
  applyBaseline(next);
@@ -13222,8 +13492,8 @@ const useVoteContainer = (elementConfig) => {
13222
13492
  const VoteContainer = memo(({ type, elementConfig, isVisible, renderElement }) => {
13223
13493
  const { root, body } = useContainerRoot({ elementConfig });
13224
13494
  const { configured, ...vote } = useVoteContainer(elementConfig);
13225
- // Без имени атрибута или хотя бы одного источника контейнеру не с чем работать: раньше он
13226
- // молча возвращал null, и опечатка в конфиге выглядела как «блока просто нет».
13495
+ // Без имени атрибута или хотя бы одного источника контейнеру не с чем работать. Ошибка вместо
13496
+ // пустоты: молчаливый `null` выдавал бы опечатку в конфиге за «блока просто нет».
13227
13497
  if (!configured) {
13228
13498
  return jsx(DataSourceError, { name: elementConfig?.templateName ?? ContainerTemplate.Vote });
13229
13499
  }
@@ -14069,8 +14339,8 @@ const useTaskNotifications = ({ enabled, suppressed, status, lastMessage, openLo
14069
14339
  const TaskContainer = memo(({ type, elementConfig, renderElement }) => {
14070
14340
  const { t, ewktGeometry } = useGlobalContext();
14071
14341
  const { dataSources, filters: selectedFilters, attributes, layerInfo, changeFilters, } = useWidgetContext(type);
14072
- const { dataSources: projectDataSources } = useWidgetContext(WidgetType.Dashboard);
14073
14342
  const { currentPage } = useWidgetPage(type);
14343
+ const dashboard = useDashboardFilterStore(type);
14074
14344
  const { taskId, runTask, stopTask, status, openLog, loading, isLogDialogOpen, closeLog, log, lastMessage, result, } = usePythonTask();
14075
14345
  const { options } = elementConfig || {};
14076
14346
  const { title, relatedResources, center, icon, statusColors, responseFilters, useNotifications, } = options || {};
@@ -14109,11 +14379,11 @@ const TaskContainer = memo(({ type, elementConfig, renderElement }) => {
14109
14379
  attributes,
14110
14380
  layerInfo,
14111
14381
  dataSources,
14112
- projectDataSources,
14382
+ dashboard,
14113
14383
  });
14114
14384
  return runTask({ resourceId, parameters: newParams, script, fileName, methodName });
14115
14385
  }));
14116
- }, [attributes, currentPage.filters, dataSources, ewktGeometry, layerInfo, projectDataSources, relatedResources, runTask, selectedFilters, stopTask, taskId]);
14386
+ }, [attributes, currentPage.filters, dashboard, dataSources, ewktGeometry, layerInfo, relatedResources, runTask, selectedFilters, stopTask, taskId]);
14117
14387
  return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), jsxs(TaskContainerWrapper, { ...body, justifyContent: center ? "center" : "flex-start", children: [jsx(StatusWaitingButton, { title: title || t("run", { ns: "dashboard", defaultValue: "Запуск" }), icon: icon, status: status, statusColors: statusColors, isWaiting: loading || !!taskId, isDisabled: !relatedResources?.length, onClick: onClick }), !!(log || taskId) && (jsxs(Fragment$1, { children: [jsx(IconButton, { kind: "info", onClick: openLog }), jsx(LogDialog, { logs: log, status: status, statusColors: statusColors, isOpen: isLogDialogOpen, onClose: onCloseLog, onMinimize: useNotifications ? onMinimizeLog : undefined })] }))] })] }));
14118
14388
  });
14119
14389
 
@@ -14515,7 +14785,7 @@ const EditAttachmentContainer = memo(({ type, elementConfig, renderElement }) =>
14515
14785
  return (jsxs(AttachmentsContainer, { id: id, style: { ...BASE_CONTAINER_STYLE, ...style }, ...bgHost, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(AttachmentsHeader, { alias: renderElement?.({ id: "alias" }), count: items.length, viewMode: viewMode, onChangeViewMode: setViewMode }), jsx(AttachmentsContent, { children: viewMode === "grid" ? (jsx(AttachmentsGrid, { items: visibleItems, isEdit: true, onPreview: handlePreview, onDelete: handleDelete })) : (jsx(AttachmentsList, { items: visibleItems, isEdit: true, onPreview: handlePreview, onDelete: handleDelete })) }), hasMore && !showMore && (jsx(ShowMoreButton, { hiddenCount: hiddenCount, onClick: handleShowMore })), jsx(AddButton, { accept: fileExtensions, onSelectFiles: uploading ? () => undefined : handleUpload, onSelectFromCatalog: handleSelectFromCatalog, onSelectFromLink: handleOpenLinkDialog }), jsx(AttachmentLinkDialog, { isOpen: isLinkDialogOpen, onClose: handleCloseLinkDialog, onSubmit: handleAddByLink }), previewIndex !== null && (jsx(Preview, { images: previewImages, initialIndex: previewIndex, isOpen: previewIndex !== null, onClose: handleClosePreview, onDownload: handleDownload }, previewIndex))] }));
14516
14786
  });
14517
14787
 
14518
- // `ProgressContainer` и `RoundedBackgroundContainer` исторически принимают `InnerContainerProps`
14788
+ // `ProgressContainer` и `RoundedBackgroundContainer` принимают `InnerContainerProps`
14519
14789
  // (см. `DataSourceInnerContainer/types.ts`). `<Name>ContainerProps` ⊆ `InnerContainerProps`
14520
14790
  // по структуре — каст оправдан, расхождение только в generic-параметре `propTypes`.
14521
14791
  const ProgressContainerTyped = ProgressContainer;
@@ -15554,10 +15824,25 @@ const ElementChips = memo(({ type, elementConfig }) => {
15554
15824
  return (jsx(DashboardChipsContainer, { style: style, children: tags.map(tag => (jsx(DashboardChip$1, { text: tag.text, "$bgColor": tag.color || colorAttr?.value || bgColor, "$fontColor": fontColor, "$fontSize": fontSize, "$isDefault": !tag.color && !colorAttr?.value }, tag.text))) }));
15555
15825
  });
15556
15826
 
15827
+ /**
15828
+ * Первое непустое значение за жизнь компонента. Дефолт контрола с подстановкой
15829
+ * (`$dashboard:scenario`) фиксируется при открытии карточки: пока фильтры дашборда грузятся, он ещё
15830
+ * пуст, а после — смена фильтра дашборда не должна затирать выбор.
15831
+ */
15832
+ const useFirstNonEmptyValue = (value) => {
15833
+ const firstValue = useRef(value);
15834
+ if (isEmptyValue(firstValue.current) && !isEmptyValue(value)) {
15835
+ firstValue.current = value;
15836
+ }
15837
+ return firstValue.current;
15838
+ };
15839
+
15557
15840
  const ElementControl = ({ elementConfig }) => {
15558
15841
  const { t } = useGlobalContext();
15559
15842
  const { attributes, layerInfo, dataSources, controls, changeControls } = useWidgetContext(WidgetType.FeatureCard);
15560
15843
  const { options, attributeName, defaultValue } = elementConfig || {};
15844
+ // Дефолт фиксируется при открытии карточки и в `controls` не пишется — только показ.
15845
+ const initialDefaultValue = useFirstNonEmptyValue(defaultValue);
15561
15846
  const { relatedDataSource, label, width, control, placeholder = t("selectValue", {
15562
15847
  ns: "dashboard",
15563
15848
  defaultValue: "Выберите значение",
@@ -15587,7 +15872,7 @@ const ElementControl = ({ elementConfig }) => {
15587
15872
  }, [control?.targetAttributeName, layerInfo?.configuration]);
15588
15873
  const currentValue = controls?.[control?.targetAttributeName] ??
15589
15874
  attribute?.value ??
15590
- defaultValue;
15875
+ initialDefaultValue;
15591
15876
  const handleChange = useCallback(([option]) => {
15592
15877
  if (control?.targetAttributeName && changeControls) {
15593
15878
  changeControls({
@@ -17323,8 +17608,8 @@ const getNextSort = (current, attributeName) => {
17323
17608
  * Css собственного бокса таблицы: размер из `options` представления плюс прокрутка того,
17324
17609
  * что в этот размер не влез.
17325
17610
  *
17326
- * Размеров нет — нет и бокса: таблица занимает столько, сколько просит содержимое, а лишнее,
17327
- * как и раньше, уходит в прокрутку области представления контейнера.
17611
+ * Размеров нет — нет и бокса: таблица занимает столько, сколько просит содержимое, а лишнее
17612
+ * уходит в прокрутку области представления контейнера.
17328
17613
  *
17329
17614
  * Проценты работают только по ширине. Между областью представления и таблицей стоит блочная
17330
17615
  * обёртка элемента (`ElementValueWrapper`) с высотой `auto`, и `height: "100%"` в ней не
@@ -17594,8 +17879,8 @@ const getDataFromRelatedFeatures = ({ t, config, filters, relatedConfig, dataSou
17594
17879
  }
17595
17880
  // Источник чарта отличается от источника фильтра наличием осей: BarChartFilter собирает
17596
17881
  // relatedConfig вручную и оси не задаёт, потому что ему нужна исходная палитра, а не растянутая
17597
- // под число элементов. Гейт смотрит и на устаревший chartAxis — иначе у старых конфигов
17598
- // молча поехали бы цвета столбцов и долей.
17882
+ // под число элементов. Гейт смотрит и на устаревший chartAxis — иначе у конфигов с плоской
17883
+ // формой оси молча поехали бы цвета столбцов и долей.
17599
17884
  const gradientArray = hasAxisConfig(relatedConfig)
17600
17885
  ? stretchPalette(colors, data.length)
17601
17886
  : colors;
@@ -17622,7 +17907,7 @@ const getDataFromRelatedFeatures = ({ t, config, filters, relatedConfig, dataSou
17622
17907
  min: filter?.attributeMin
17623
17908
  ? feature.properties[filter.attributeMin]
17624
17909
  : null,
17625
- max: filter?.attributeMin
17910
+ max: filter?.attributeMax
17626
17911
  ? feature.properties[filter.attributeMax]
17627
17912
  : null,
17628
17913
  },
@@ -17992,7 +18277,7 @@ const isContainerRef = (element) => "containerId" in element &&
17992
18277
  !("templateName" in element) &&
17993
18278
  !("type" in element) &&
17994
18279
  !("children" in element);
17995
- const getRenderElement = ({ t, config, elementConfig, attributes = [], layerInfo, selectedTabId, setSelectedTabId, pageIndex, expandedContainers, onChange, type = WidgetType.FeatureCard, }) => (props) => {
18280
+ const getRenderElement = ({ t, config, elementConfig, attributes = [], layerInfo, selectedTabId, setSelectedTabId, pageIndex, expandedContainers, onChange, type = WidgetType.FeatureCard, configStringSources, }) => (props) => {
17996
18281
  const { id, index, wrap = true } = props;
17997
18282
  const currentConfig = elementConfig || config;
17998
18283
  let element = id
@@ -18009,6 +18294,12 @@ const getRenderElement = ({ t, config, elementConfig, attributes = [], layerInfo
18009
18294
  container = container[0];
18010
18295
  element = container || undefined;
18011
18296
  }
18297
+ // Строки узла — от атрибутов этого рендера: у строк `DataSource` они свои, не карточки.
18298
+ element = resolveElementStrings(element, configStringSources && {
18299
+ ...configStringSources,
18300
+ attributes: attributes.length ? attributes : configStringSources.attributes,
18301
+ layerInfo: layerInfo ?? configStringSources.layerInfo,
18302
+ });
18012
18303
  const { options, children, templateName } = element || {};
18013
18304
  const { tabId, hideEmpty, expandable, expanded, innerTemplateName } = options || {};
18014
18305
  if (isNotValidSelectedTab(tabId, selectedTabId))
@@ -18025,6 +18316,7 @@ const getRenderElement = ({ t, config, elementConfig, attributes = [], layerInfo
18025
18316
  pageIndex,
18026
18317
  onChange,
18027
18318
  type,
18319
+ configStringSources,
18028
18320
  });
18029
18321
  // Ставится внутри `getDefaultContainer` — единственного места, которое знает по построению,
18030
18322
  // что значение оказалось контейнером. Коллбэк вызывается синхронно, читаем строго после.
@@ -18071,7 +18363,12 @@ const getRenderElement = ({ t, config, elementConfig, attributes = [], layerInfo
18071
18363
  const useModalContent = (type, modalId) => {
18072
18364
  const { config } = useWidgetConfig(type);
18073
18365
  const { expandedContainers, attributes } = useWidgetContext(type);
18074
- const modalConfig = useMemo(() => (config?.modals ?? []).find(({ id }) => id === modalId) ?? null, [config?.modals, modalId]);
18366
+ const configStringSources = useConfigStringSources(type);
18367
+ // Заголовок модалки (`options.title`) — тоже строка конфига с подстановками.
18368
+ const modalConfig = useMemo(() => {
18369
+ const modal = (config?.modals ?? []).find(({ id }) => id === modalId) ?? null;
18370
+ return resolveElementStrings(modal, configStringSources);
18371
+ }, [config?.modals, configStringSources, modalId]);
18075
18372
  const modalContent = useMemo(() => (modalConfig?.children ?? []), [modalConfig]);
18076
18373
  const modalSlots = useMemo(() => modalContent.filter(({ id }) => MODAL_SLOT_IDS.includes(id)), [modalContent]);
18077
18374
  const renderElement = useMemo(() => getRenderElement({
@@ -18080,14 +18377,16 @@ const useModalContent = (type, modalId) => {
18080
18377
  elementConfig: { children: modalContent },
18081
18378
  attributes,
18082
18379
  expandedContainers,
18083
- }), [type, config, modalContent, attributes, expandedContainers]);
18380
+ configStringSources,
18381
+ }), [type, config, modalContent, attributes, expandedContainers, configStringSources]);
18084
18382
  const renderSlot = useMemo(() => getRenderElement({
18085
18383
  type,
18086
18384
  config,
18087
18385
  elementConfig: { children: modalSlots },
18088
18386
  attributes,
18089
18387
  expandedContainers,
18090
- }), [type, config, modalSlots, attributes, expandedContainers]);
18388
+ configStringSources,
18389
+ }), [type, config, modalSlots, attributes, expandedContainers, configStringSources]);
18091
18390
  const hasTitleIcon = useMemo(() => modalSlots.some(({ id }) => id === MODAL_TITLE_ICON_SLOT_ID), [modalSlots]);
18092
18391
  const hasBgImage = useMemo(() => hasContainerBgImage({ children: modalSlots }), [modalSlots]);
18093
18392
  return { modalConfig, modalContent, renderElement, renderSlot, hasTitleIcon, hasBgImage };
@@ -18258,9 +18557,8 @@ const getFeatureCardHeader = (templateName) => {
18258
18557
  /**
18259
18558
  * Ширина фильтра из конфига. Без явного значения фильтр занимает всю ширину контейнера.
18260
18559
  *
18261
- * Раньше у каждого фильтра был свой пиксельный фолбэк, подогнанный под левую панель приложения
18262
- * (288px у Dropdown, 17.25rem у Text, 17rem у RangeNumber, ничего у Tree), из-за чего в одной
18263
- * панели фильтры вставали «лесенкой» — и тем заметнее, чем шире контейнер.
18560
+ * Фолбэк общий для всех типов фильтров: свои пиксельные фолбэки у разных типов выстраивали бы
18561
+ * фильтры одной панели «лесенкой» — и тем заметнее, чем шире контейнер.
18264
18562
  */
18265
18563
  const getFilterWidth = (width) => toCssSize(width) ?? "100%";
18266
18564
 
@@ -18747,7 +19045,7 @@ const RangeDateFilter = ({ type, filter }) => {
18747
19045
  const maxDate = useMemo(() => maxValue || maxFromData?.toISOString(), [maxFromData, maxValue]);
18748
19046
  // Диапазон по умолчанию: `defaultValue: [from, to]` из конфига фильтра — начальное значение
18749
19047
  // и точка сброса. Границы календаря (min/max) при этом не меняются; незаданная граница
18750
- // дефолта падает на них же — как было до появления `defaultValue`.
19048
+ // дефолта падает на них же — как и без `defaultValue`.
18751
19049
  const defaultRange = useMemo(() => {
18752
19050
  const [from, to] = (Array.isArray(configFilter?.defaultValue) ? configFilter.defaultValue : []);
18753
19051
  return [getDate(from)?.toISOString() ?? minDate, getDate(to)?.toISOString() ?? maxDate];
@@ -19393,21 +19691,23 @@ const getFilterValue = ({ selectedFilters, configFilters, filterName, newValue,
19393
19691
  /** Условие источника задаётся строкой или массивом строк — приводим к массиву для единообразного обхода. */
19394
19692
  const toConditionsArray = (value) => Array.isArray(value) ? value : value ? [value] : [];
19395
19693
 
19396
- const MAP_VIEW_PLACEHOLDERS = MAP_VIEW_FILTER_NAMES.map(name => `${FILTER_PREFIX}${name}`);
19397
- // Та же граница, что и при подстановке в `formatDataSourceCondition`: составные формы
19398
- // (`%zoomLevel`, `%extent_id`, `%zoom.min`) принадлежат пользовательским фильтрам, не карте.
19399
- const hasMapViewPlaceholder = (value) => MAP_VIEW_PLACEHOLDERS.some(placeholder => new RegExp(`${placeholder}(?![\\w.])`).test(value));
19694
+ /**
19695
+ * Строка ссылается на вид карты — `%extent` / `%zoom` целиком или внутри строки. Граница та же,
19696
+ * что у резолвера строк конфига: составные формы (`%zoomLevel`, `%extent_id`, `%zoom.min`)
19697
+ * принадлежат пользовательским фильтрам, не карте.
19698
+ */
19699
+ const hasMapViewPlaceholder = (value) => typeof value === "string" &&
19700
+ tokenizeConfigString(value).some(segment => typeof segment !== "string" &&
19701
+ segment.kind === "filter" &&
19702
+ segment.scope === "widget" &&
19703
+ !segment.prop &&
19704
+ MAP_VIEW_FILTER_NAMES.includes(segment.name));
19400
19705
  /**
19401
19706
  * Источники, чей запрос зависит от текущего вида карты — в `parameters` или `condition` у них
19402
19707
  * есть `%extent` / `%zoom`. Перезапрашивают при движении карты только их: иначе каждый пан
19403
19708
  * дёргал бы все запросы страницы.
19404
19709
  */
19405
- const getMapViewDataSources = (dataSources) => dataSources?.filter(({ parameters, condition }) => {
19406
- const hasInParameters = Object.values(parameters ?? {}).some(value => typeof value === "string" && MAP_VIEW_PLACEHOLDERS.includes(value));
19407
- if (hasInParameters)
19408
- return true;
19409
- return toConditionsArray(condition).some(hasMapViewPlaceholder);
19410
- }) ?? [];
19710
+ const getMapViewDataSources = (dataSources) => dataSources?.filter(({ parameters, condition }) => Object.values(parameters ?? {}).some(hasMapViewPlaceholder) || toConditionsArray(condition).some(hasMapViewPlaceholder)) ?? [];
19411
19711
 
19412
19712
  const getFormattedAttributes = (t, data, attributes, config) => {
19413
19713
  const showOtherItems = config?.options?.otherItems < data?.length;
@@ -19528,8 +19828,6 @@ const getLayerInfo = (layer) => {
19528
19828
 
19529
19829
  const getLayerInfoFromDataSources = (layerInfos, dataSources, relatedDataSource) => layerInfos?.find(({ name }) => name === dataSources?.find(item => item.name === relatedDataSource)?.layerName);
19530
19830
 
19531
- const getPagesFromConfig = (config) => config?.children?.[0]?.children;
19532
-
19533
19831
  const getPagesFromProjectInfo = (projectInfo) => getPagesFromConfig(projectInfo?.content?.dashboardConfiguration);
19534
19832
 
19535
19833
  const getRelatedAttribute = (layerInfo, sourceAttributeName, relatedLayerName) => {
@@ -19647,6 +19945,27 @@ const removeDataSource = (dashboardConfiguration, name, pageIndex) => {
19647
19945
  return newConfig;
19648
19946
  };
19649
19947
 
19948
+ /**
19949
+ * Датасорсы, которые нужно перезапросить после изменения фильтров: их параметры ссылаются на
19950
+ * изменившийся фильтр виджета (`%name`, в том числе через дефолт `$dashboard:`) или дашборда
19951
+ * (`$dashboard:name`). Датасорсы слоя без `query` перезапрашиваются на любое изменение.
19952
+ * Нечего перезапрашивать — `undefined`.
19953
+ */
19954
+ const selectUpdatingDataSources = ({ configDataSources, filters, configFilters, dashboard, prev, }) => {
19955
+ const widgetNames = [
19956
+ ...getChangedFilterNames(filters, prev.filters ?? {}, configFilters),
19957
+ ...getChangedDefaultNames(configFilters, prev.configFilters, filters),
19958
+ ];
19959
+ const dashboardNames = dashboard
19960
+ ? getChangedFilterNames(dashboard.selectedFilters, prev.dashboardFilters ?? {}, dashboard.configFilters)
19961
+ : [];
19962
+ if (!widgetNames.length && !dashboardNames.length)
19963
+ return undefined;
19964
+ const dataSources = configDataSources?.filter(({ parameters = {}, query }) => isNil(query) ||
19965
+ Object.values(parameters).some(value => referencesFilters({ value, widgetNames, dashboardNames, hasDashboardStore: !!dashboard })));
19966
+ return dataSources?.length ? dataSources : undefined;
19967
+ };
19968
+
19650
19969
  const updateDataSource = (dashboardConfiguration, name, pageIndex, data) => {
19651
19970
  const newConfig = JSON.parse(JSON.stringify(dashboardConfiguration));
19652
19971
  if (newConfig.dataSources?.length) {
@@ -20933,5 +21252,5 @@ const DEFAULT_HEATMAP_STYLE = {
20933
21252
  ],
20934
21253
  };
20935
21254
 
20936
- export { ALIGNMENTS, ALIGN_ITEMS, ATTRIBUTE_ICON_ELEMENT_TYPES, AXIS_TITLE_GAP, AXIS_TITLE_LINE_HEIGHT, AXIS_TITLE_TOP_GRID_GAP, AXIS_TITLE_TOP_HEIGHT, AddButtonRow, AddFeatureButton, AddFeatureContainer, AlertIconContainer, AttachmentContainer, AttributeGalleryContainer, AttributeLabel, BASE_CONTAINER_STYLE, BG_IMAGE_SLOT_ID, BaseMapTheme, CHART_AXIS_ALIGN_JUSTIFY, CHART_TYPES, COMPACT_FRACTION_DIGITS, CONFIG_PAGES_ID, CONFIG_PAGE_ID, CONTAINERS_GROUP_DEFAULTS, CONTAINER_BODY_ATTRIBUTE, CONTAINER_BODY_FILL_STYLE, CameraContainer, Chart, ChartContainer, ChartLegend, ChartLoading, Container, ContainerBackground, ContainerChildren, ContainerLoading, ContainerRoot, ContainerTemplate, ContainerWrapper, ContainersGroupContainer, DASHBOARD_OVERLAY_Z_INDEX, DEFAULT_ATTRIBUTE_NAME, DEFAULT_BARCHART_RADIUS, DEFAULT_BASE_MAP, DEFAULT_BLUR, DEFAULT_CHART_ANGLE, DEFAULT_CHART_HEIGHT, DEFAULT_CHART_WIDTH, DEFAULT_CIRCLE_RADIUS, DEFAULT_CIRCLE_STROKE_WIDTH, DEFAULT_CIRCLE_STYLE, DEFAULT_COLOR, DEFAULT_DASHBOARD_CONFIG, DEFAULT_DATA_SOURCE_LIMIT, DEFAULT_DROPDOWN_WIDTH, DEFAULT_FILL_EXTRUSION_BASE, DEFAULT_FILL_EXTRUSION_HEIGHT, DEFAULT_FILL_EXTRUSION_STYLE, DEFAULT_FILL_EXTRUSION_VERTICAL_GRADIENT, DEFAULT_FILL_STYLE, DEFAULT_FILTER_PADDING, DEFAULT_GRID_GAP, DEFAULT_HEATMAP_COLOR, DEFAULT_HEATMAP_INTENSITY, DEFAULT_HEATMAP_RADIUS, DEFAULT_HEATMAP_STYLE, DEFAULT_HEATMAP_WEIGHT, DEFAULT_ICON_ANCHOR, DEFAULT_ICON_OVERLAP, DEFAULT_ICON_PADDING, DEFAULT_ICON_ROTATE, DEFAULT_ICON_SIZE, DEFAULT_ID_ATTRIBUTE_NAME, DEFAULT_LAT, DEFAULT_LINE_CAP, DEFAULT_LINE_JOIN, DEFAULT_LINE_STYLE, DEFAULT_LINE_WIDTH, DEFAULT_LNG, DEFAULT_OPACITY, DEFAULT_PAGES_CONFIG, DEFAULT_PIECHART_RADIUS, DEFAULT_SYMBOL_COLOR, DEFAULT_SYMBOL_HALO_COLOR, DEFAULT_SYMBOL_PLACEMENT, DEFAULT_SYMBOL_SPACING, DEFAULT_SYMBOL_STYLE, DEFAULT_TEXT_ANCHOR, DEFAULT_TEXT_JUSTIFY, DEFAULT_TEXT_SIZE, DEFAULT_TEXT_TRANSFORM, DEFAULT_TRACK_FR, DEFAULT_TRANSLATE, DEFAULT_ZOOM, DRAG_THRESHOLD_PX, Dashboard, DashboardCheckbox, DashboardChip, DashboardContent, DashboardContext, DashboardDefaultHeader, DashboardHeader, DashboardLoading, DashboardPlaceholder, DashboardPlaceholderWrap, DashboardProvider, DashboardWrapper, DataSourceContainer, DataSourceError, DataSourceErrorContainer, DataSourceInnerContainer, DataSourceProgressContainer, DateFormat, DefaultAttributesContainer, DefaultHeaderContainer, DefaultHeaderWrapper, DividerContainer, EMPTY_DATA_SOURCE_LAYER_INFO, EXTENT_FILTER_NAME, EditGeometryType, ElementButton, ElementCamera, ElementChart, ElementChips, ElementControl, ElementIcon, ElementImage, ElementLegend, ElementLink, ElementMarkdown, ElementSlideshow, ElementSvg, ElementTable, ElementTooltip, ElementValueWrapper, ExpandableTitle, FEATURE_CARD_DEFAULT_COLORS, FEATURE_CARD_OTHER_COLOR, FILL_SIZE, FILTERED_VALUE_OPACITY, FILTER_PREFIX, FR_PRECISION, FeatureCardBackgroundHeader, FeatureCardButtons, FeatureCardContext, FeatureCardDefaultHeader, FeatureCardHeader, FeatureCardProvider, FeatureCardSlideshowHeader, FeatureCardTitle, FeatureControls, FeatureTitleContainer, FiltersContainer, GEOMETRY_ATTRIBUTE, GEOMETRY_FILTER_NAME, GRID_AUTO_FILL_DEFAULTS, GRID_CELL_ATTR, GRID_CELL_ID_PREFIX, GRID_DRAGGING_ATTR, GRID_DRAG_SOURCE_ATTR, GRID_DROP_TARGET_ATTR, GRID_FILL_DEFAULTS, GRID_ROW_ID_PREFIX, GlobalContext, GlobalProvider, GridRowContainer, HANDLE_BLEED_PX, Header, HeaderContainer, HeaderFontColorMixin, HeaderFrontView, HeaderTemplate, HeaderTitleContainer, HiddenTitleItems, IconContainer, ImageContainer, LEFT_PANEL_HEADER_HEIGHT, LINE_CHART_X_LABELS_BLANK, LINE_CHART_Y_TICKS, Layer, LayerDescription, LayerGroupList, LayerIcon, LayerIconContainer, LayerListContainer, LayerTree, LayersContainer, LayersListWrapper, LinearProgressContainer, LogTerminal, LogoContainer, MAP_VIEW_FILTER_NAMES, MAX_CHART_WIDTH, MAX_TRACKS, MIN_TRACK_PX, MIN_TRACK_RATIO, MODAL_HEADER_BG_POSITIONS, Map$1 as Map, MapContext, MapProvider, NON_TRACK_SLOT_IDS, NO_CELL_DRAG_SELECTOR, NO_CONTENT_VALUE, NUMERIC_ATTRIBUTE_TYPES, NoLiveSnapshotContainer, OBJECT_FITS, OVERFLOWS, OneColumnContainer, PIE_CHART_TOOLTIP_STYLE, POLL_SUBTASK_INTERVAL_MS, POLL_SUBTASK_TIMEOUT_MS, PROJECT_ALIAS_PROP, PROJECT_FILTER_NAME, PROJECT_NAME_PROP, PROJECT_PROPS, PROVIDER_PREFIX, PageNavigator, PageTitle, PageTitleContainer, PagesContainer, Pagination, PresentationHeader, PresentationHeaderButtons, PresentationHeaderTools, PresentationPanelContainer, PresentationPanelWrapper, PresentationWrapper, ProgressContainer, ProviderPrefix, QUERY_DESCRIPTION_CACHE_TTL, RESIZE_HANDLE_ATTR, ResizeHandle, RoundedBackgroundContainer, SAVE_HOOK_RESULT_DURATION, SERVER_NOTIFICATION_EVENT, STACK_BAR_TOTAL_HEIGHT, ScalingFactor, ServerNotificationsContext, ServerNotificationsProvider, SlideshowContainer, SmallPreviewContainer$1 as SmallPreviewContainer, SmallPreviewControl, SmallPreviewCounter, SmallPreviewImages, SmallPreviewLeft, SmallPreviewRight, StackBar, StructuredDataContainer, SvgImage, TILE_ALIGNMENTS, TIME_ZONE_FORMAT, TITLE_SLOT_IDS, TabsContainer, TextTrim, ThemeName, TitleContainer, TopContainer, TopContainerButtons, TwoColumnContainer, UploadContainer, VIEW_MODES, VoteContainer, WidgetType, X_AXIS_TITLE_GAP, X_AXIS_TITLE_LEGEND_GAP, Y_AXIS_LABEL_OVERHANG, ZOOM_FILTER_NAME, addDataSource, addDataSources, adjustColor, applyFiltersToCondition, applyQueryFilters, applyTreeFilterToCondition, applyVarsToCondition, asAttributeName, asChartId, asContainerId, asDataSourceName, asFilterName, asLayerName, asModalId, asResourceId, asTabId, buildGridTemplate, buildTrackTemplate, checkEqualOrIncludes, checkIsLoading, collectConfigIds, colorToHex, containsNodeId, createConfigLayer, createConfigPage, createGridCell, createGridIdFactory, createGridRow, createNewPageId, createSaveNotificationId, dateOptions, debounce, decimalOpacityToHex, enrichStyleItemsWithIds, enrichStyleModelsWithIds, eqlParametersToPayload, fetchQueryDescription, findAttributeInExpression, findCellContext, formatArea, formatAttributeValue, formatChartRelatedValue, formatConditionValue, formatDataSourceCondition, formatDate$1 as formatDate, formatElementValue, formatLength, formatNumber, formatPolygonMeasure, geometryToEwkt, getActualExtrusionHeight, getAttributeByName, getAttributeConfigurationByName, getAttributeIconElement, getAttributeIconUrl, getAttributeValue, getAttributesConfiguration, getAverageTrackSize, getAxisSide, getChartAxes, getChartFilterName, getChartMarkers, getConfigFilter, getContainerComponent, getDashboardHeader, getDataFromAttributes, getDataFromRelatedFeatures, getDataSource, getDataSourceFilterValue, getDataSourceLayerInfo, getDate, getDefaultConfig, getDisplayTemplateNameFromAttribute, getElementValue, getFeatureAttributes, getFeatureCardHeader, getFilterComponent, getFilterSelectedItems, getFilterValue, getFormattedAttributes, getGradientColors, getImageUrl, getLayerClientStyle, getLayerInfo, getLayerInfoAttribute, getLayerInfoFromDataSources, getLayoutChildren, getMapViewDataSources, getModalsDataSources, getPagesFromConfig, getPagesFromProjectInfo, getProjectValue, getProxyService, getRelatedAttribute, getRenderElement, getResourceUrl, getRootElementId, getSelectedFilterValue, getShownItemsLimit, getSlideshowImages, getStyleAttributes, getSvgUrl, getTemplateNameFromAttribute, getThemeByName, getTotalFromAttributes, getTotalFromRelatedFeatures, getTrackSize, getTrackSizeKey, getTrackSizes, getWrapperSizeStyle, hasAxisConfig, hasContainerBgImage, hexToRgba, isCrossOriginUrl, isEmptyElementValue, isEmptyValue, isFeaturesFilterValue, isFillSize, isFrSize, isGridNode, isHiddenEmptyValue, isHookActive, isLayerService, isNotValidSelectedTab, isNumeric, isObject, isProxyService, isRightAxis, isRootOwningContainer, isTreeFilterValue, isValueAxis, isVisibleContainer, mapNodeById, mergeAttributeConfigurations, metersPerPixel, noMarginMixin, numberOptions, parseFrValue, parseIconNames, parseIconNamesFromClientStyle, pieChartTooltipFromAttributes, pieChartTooltipFromRelatedFeatures, pointOptions, removeDataSource, removeTracks, replaceNodesByIds, resolveAxis, rgbToHex, roundFr, roundTotalSum, setLayoutChildren, sizeCssMixin, sliceShownOtherItems, stretchPalette, timeOptions, toConditionsArray, toCssSize, toFrSize, toPxNumber, toRenderableValue, tooltipNameFromAttributes, tooltipValueFromAttributes, tooltipValueFromRelatedFeatures, transparentizeColor, updateDataSource, useAfterSave, useAppHeight, useAttachmentDownload, useAttachmentItems, useAttachmentPreviewImages, useAttachmentsView, useAutoCompleteControl, useBeforeSave, useBgImageHost, useChartAxisAttributes, useChartAxisTickFormat, useChartAxisTitles, useChartChange, useChartData, useConfigDataSources, useContainerAttributes, useContainerRoot, useCurrentPageLayers, useCustomFeatureSelect, useDashboardHeader, useDataSourceLoading, useDataSources, useDebouncedCallback, useDiffPage, useEditGroupAttributes, useEqualTileWidth, useExpandableContainers, useExportPdf, useFeatureSaveHooks, useFetchImageWithAuth, useFetchWithAuth, useGetConfigLayer, useGlobalContext, useHeaderRender, useHideIfEmptyDataSource, useLayerHiddenAttributes, useLayerParams, useMapContext, useMapDraw, useMapImages, useMaxZoomTo, useProjectDashboardInit, usePythonSandbox, usePythonTask, useRedrawLayer, useRelatedDataSourceAttributes, useRemoteTask, useRenderElement, useResizeBox, useResizeDrag, useSavePrototypeBuilder, useServerNotificationsContext, useShownOtherItems, useToggle, useUpdateDataSource, useVisibleProjectItems, useWidgetConfig, useWidgetContext, useWidgetFilters, useWidgetPage, useWindowResize, useWrapperSize, useZoomToFeatures, useZoomToPoint, withTrackSize };
21255
+ export { ALIGNMENTS, ALIGN_ITEMS, ATTRIBUTE_ICON_ELEMENT_TYPES, AXIS_TITLE_GAP, AXIS_TITLE_LINE_HEIGHT, AXIS_TITLE_TOP_GRID_GAP, AXIS_TITLE_TOP_HEIGHT, AddButtonRow, AddFeatureButton, AddFeatureContainer, AlertIconContainer, AttachmentContainer, AttributeGalleryContainer, AttributeLabel, BASE_CONTAINER_STYLE, BG_IMAGE_SLOT_ID, BaseMapTheme, CHART_AXIS_ALIGN_JUSTIFY, CHART_TYPES, COMPACT_FRACTION_DIGITS, CONFIG_PAGES_ID, CONFIG_PAGE_ID, CONFIG_STRING_OPTION_KEYS, CONFIG_TEXT_ARRAY_SEPARATOR, CONFIG_TOKEN_REGEXP, CONTAINERS_GROUP_DEFAULTS, CONTAINER_BODY_ATTRIBUTE, CONTAINER_BODY_FILL_STYLE, CameraContainer, Chart, ChartContainer, ChartLegend, ChartLoading, Container, ContainerBackground, ContainerChildren, ContainerLoading, ContainerRoot, ContainerTemplate, ContainerWrapper, ContainersGroupContainer, DASHBOARD_OVERLAY_Z_INDEX, DEFAULT_ATTRIBUTE_NAME, DEFAULT_BARCHART_RADIUS, DEFAULT_BASE_MAP, DEFAULT_BLUR, DEFAULT_CHART_ANGLE, DEFAULT_CHART_HEIGHT, DEFAULT_CHART_WIDTH, DEFAULT_CIRCLE_RADIUS, DEFAULT_CIRCLE_STROKE_WIDTH, DEFAULT_CIRCLE_STYLE, DEFAULT_COLOR, DEFAULT_DASHBOARD_CONFIG, DEFAULT_DATA_SOURCE_LIMIT, DEFAULT_DROPDOWN_WIDTH, DEFAULT_FILL_EXTRUSION_BASE, DEFAULT_FILL_EXTRUSION_HEIGHT, DEFAULT_FILL_EXTRUSION_STYLE, DEFAULT_FILL_EXTRUSION_VERTICAL_GRADIENT, DEFAULT_FILL_STYLE, DEFAULT_FILTER_PADDING, DEFAULT_GRID_GAP, DEFAULT_HEATMAP_COLOR, DEFAULT_HEATMAP_INTENSITY, DEFAULT_HEATMAP_RADIUS, DEFAULT_HEATMAP_STYLE, DEFAULT_HEATMAP_WEIGHT, DEFAULT_ICON_ANCHOR, DEFAULT_ICON_OVERLAP, DEFAULT_ICON_PADDING, DEFAULT_ICON_ROTATE, DEFAULT_ICON_SIZE, DEFAULT_ID_ATTRIBUTE_NAME, DEFAULT_LAT, DEFAULT_LINE_CAP, DEFAULT_LINE_JOIN, DEFAULT_LINE_STYLE, DEFAULT_LINE_WIDTH, DEFAULT_LNG, DEFAULT_OPACITY, DEFAULT_PAGES_CONFIG, DEFAULT_PIECHART_RADIUS, DEFAULT_SYMBOL_COLOR, DEFAULT_SYMBOL_HALO_COLOR, DEFAULT_SYMBOL_PLACEMENT, DEFAULT_SYMBOL_SPACING, DEFAULT_SYMBOL_STYLE, DEFAULT_TEXT_ANCHOR, DEFAULT_TEXT_JUSTIFY, DEFAULT_TEXT_SIZE, DEFAULT_TEXT_TRANSFORM, DEFAULT_TRACK_FR, DEFAULT_TRANSLATE, DEFAULT_ZOOM, DRAG_THRESHOLD_PX, Dashboard, DashboardCheckbox, DashboardChip, DashboardContent, DashboardContext, DashboardDefaultHeader, DashboardHeader, DashboardLoading, DashboardPlaceholder, DashboardPlaceholderWrap, DashboardProvider, DashboardWrapper, DataSourceContainer, DataSourceError, DataSourceErrorContainer, DataSourceInnerContainer, DataSourceProgressContainer, DateFormat, DefaultAttributesContainer, DefaultHeaderContainer, DefaultHeaderWrapper, DividerContainer, EMPTY_DATA_SOURCE_LAYER_INFO, EXTENT_FILTER_NAME, EditGeometryType, ElementButton, ElementCamera, ElementChart, ElementChips, ElementControl, ElementIcon, ElementImage, ElementLegend, ElementLink, ElementMarkdown, ElementSlideshow, ElementSvg, ElementTable, ElementTooltip, ElementValueWrapper, ExpandableTitle, FEATURE_CARD_DEFAULT_COLORS, FEATURE_CARD_OTHER_COLOR, FILL_SIZE, FILTERED_VALUE_OPACITY, FILTER_PREFIX, FR_PRECISION, FeatureCardBackgroundHeader, FeatureCardButtons, FeatureCardContext, FeatureCardDefaultHeader, FeatureCardHeader, FeatureCardProvider, FeatureCardSlideshowHeader, FeatureCardTitle, FeatureControls, FeatureTitleContainer, FiltersContainer, GEOMETRY_ATTRIBUTE, GEOMETRY_FILTER_NAME, GRID_AUTO_FILL_DEFAULTS, GRID_CELL_ATTR, GRID_CELL_ID_PREFIX, GRID_DRAGGING_ATTR, GRID_DRAG_SOURCE_ATTR, GRID_DROP_TARGET_ATTR, GRID_FILL_DEFAULTS, GRID_ROW_ID_PREFIX, GlobalContext, GlobalProvider, GridRowContainer, HANDLE_BLEED_PX, Header, HeaderContainer, HeaderFontColorMixin, HeaderFrontView, HeaderTemplate, HeaderTitleContainer, HiddenTitleItems, IconContainer, ImageContainer, LEFT_PANEL_HEADER_HEIGHT, LINE_CHART_X_LABELS_BLANK, LINE_CHART_Y_TICKS, Layer, LayerDescription, LayerGroupList, LayerIcon, LayerIconContainer, LayerListContainer, LayerTree, LayersContainer, LayersListWrapper, LinearProgressContainer, LogTerminal, LogoContainer, MAP_VIEW_FILTER_NAMES, MAX_CHART_WIDTH, MAX_TRACKS, MIN_TRACK_PX, MIN_TRACK_RATIO, MODAL_HEADER_BG_POSITIONS, Map$1 as Map, MapContext, MapProvider, NON_TRACK_SLOT_IDS, NO_CELL_DRAG_SELECTOR, NO_CONTENT_VALUE, NUMERIC_ATTRIBUTE_TYPES, NoLiveSnapshotContainer, OBJECT_FITS, OVERFLOWS, OneColumnContainer, PIE_CHART_TOOLTIP_STYLE, POLL_SUBTASK_INTERVAL_MS, POLL_SUBTASK_TIMEOUT_MS, PROJECT_ALIAS_PROP, PROJECT_FILTER_NAME, PROJECT_NAME_PROP, PROJECT_PROPS, PROVIDER_PREFIX, PageNavigator, PageTitle, PageTitleContainer, PagesContainer, Pagination, PresentationHeader, PresentationHeaderButtons, PresentationHeaderTools, PresentationPanelContainer, PresentationPanelWrapper, PresentationWrapper, ProgressContainer, ProviderPrefix, QUERY_DESCRIPTION_CACHE_TTL, RANGE_PROPS, RESIZE_HANDLE_ATTR, ResizeHandle, RoundedBackgroundContainer, SAVE_HOOK_RESULT_DURATION, SERVER_NOTIFICATION_EVENT, STACK_BAR_TOTAL_HEIGHT, ScalingFactor, ServerNotificationsContext, ServerNotificationsProvider, SlideshowContainer, SmallPreviewContainer$1 as SmallPreviewContainer, SmallPreviewControl, SmallPreviewCounter, SmallPreviewImages, SmallPreviewLeft, SmallPreviewRight, StackBar, StructuredDataContainer, SvgImage, TILE_ALIGNMENTS, TIME_ZONE_FORMAT, TITLE_SLOT_IDS, TabsContainer, TextTrim, ThemeName, TitleContainer, TopContainer, TopContainerButtons, TwoColumnContainer, UploadContainer, VIEW_MODES, VoteContainer, WidgetType, X_AXIS_TITLE_GAP, X_AXIS_TITLE_LEGEND_GAP, Y_AXIS_LABEL_OVERHANG, ZOOM_FILTER_NAME, addDataSource, addDataSources, adjustColor, applyQueryFilters, applyTreeFilterToCondition, applyVarsToCondition, asAttributeName, asChartId, asContainerId, asDataSourceName, asFilterName, asLayerName, asModalId, asResourceId, asTabId, buildGridTemplate, buildTrackTemplate, checkEqualOrIncludes, checkIsLoading, collectConfigIds, colorToHex, containsNodeId, createConfigLayer, createConfigPage, createGridCell, createGridIdFactory, createGridRow, createNewPageId, createSaveNotificationId, dateOptions, debounce, decimalOpacityToHex, enrichStyleItemsWithIds, enrichStyleModelsWithIds, eqlParametersToPayload, fetchQueryDescription, findAttributeInExpression, findCellContext, formatArea, formatAttributeValue, formatChartRelatedValue, formatConditionSection, formatConditionString, formatConditionValue, formatDataSourceCondition, formatDate$1 as formatDate, formatElementValue, formatLength, formatNumber, formatPolygonMeasure, geometryToEwkt, getActualExtrusionHeight, getAttributeByName, getAttributeConfigurationByName, getAttributeIconElement, getAttributeIconUrl, getAttributeValue, getAttributesConfiguration, getAverageTrackSize, getAxisSide, getChangedDefaultNames, getChangedFilterNames, getChartAxes, getChartFilterName, getChartMarkers, getConfigFilter, getContainerComponent, getDashboardHeader, getDataFromAttributes, getDataFromRelatedFeatures, getDataSource, getDataSourceFilterValue, getDataSourceLayerInfo, getDate, getDefaultConfig, getDisplayTemplateNameFromAttribute, getElementValue, getFeatureAttributes, getFeatureCardHeader, getFilterComponent, getFilterSelectedItems, getFilterTokenValue, getFilterValue, getFormattedAttributes, getGradientColors, getImageUrl, getLayerClientStyle, getLayerInfo, getLayerInfoAttribute, getLayerInfoFromDataSources, getLayoutChildren, getMapViewDataSources, getModalsDataSources, getPageFilters, getPagesFromConfig, getPagesFromProjectInfo, getProjectValue, getProxyService, getRelatedAttribute, getRenderElement, getResourceUrl, getRootElementId, getSelectedFilterValue, getShownItemsLimit, getSingleToken, getSlideshowImages, getStyleAttributes, getSvgUrl, getTemplateNameFromAttribute, getThemeByName, getTotalFromAttributes, getTotalFromRelatedFeatures, getTrackSize, getTrackSizeKey, getTrackSizes, getWrapperSizeStyle, hasAxisConfig, hasContainerBgImage, hexToRgba, isCrossOriginUrl, isDashboardReference, isEmptyElementValue, isEmptyValue, isFeaturesFilterValue, isFillSize, isFrSize, isGridNode, isHiddenEmptyValue, isHookActive, isLayerService, isNotValidSelectedTab, isNumeric, isObject, isProxyService, isRightAxis, isRootOwningContainer, isTreeFilterValue, isValueAxis, isVisibleContainer, joinConfigSegments, mapNodeById, mergeAttributeConfigurations, metersPerPixel, noMarginMixin, numberOptions, parseFrValue, parseIconNames, parseIconNamesFromClientStyle, pieChartTooltipFromAttributes, pieChartTooltipFromRelatedFeatures, pointOptions, referencesFilters, removeDataSource, removeTracks, replaceNodesByIds, resolveAxis, resolveConfigString, resolveConfigToken, resolveElementStrings, resolveFilterDefaults, resolveQueryParameter, rgbToHex, roundFr, roundTotalSum, selectUpdatingDataSources, setLayoutChildren, sizeCssMixin, sliceShownOtherItems, stretchPalette, timeOptions, toConditionsArray, toCssSize, toFrSize, toPxNumber, toRenderableValue, tokenizeConfigString, tooltipNameFromAttributes, tooltipValueFromAttributes, tooltipValueFromRelatedFeatures, transparentizeColor, updateDataSource, useAfterSave, useAppHeight, useAttachmentDownload, useAttachmentItems, useAttachmentPreviewImages, useAttachmentsView, useAutoCompleteControl, useBeforeSave, useBgImageHost, useChartAxisAttributes, useChartAxisTickFormat, useChartAxisTitles, useChartChange, useChartData, useConfigDataSources, useConfigStringSources, useContainerAttributes, useContainerRoot, useCurrentPageLayers, useCustomFeatureSelect, useDashboardFilterStore, useDashboardHeader, useDataSourceLoading, useDataSources, useDebouncedCallback, useDiffPage, useEditGroupAttributes, useEqualTileWidth, useExpandableContainers, useExportPdf, useFeatureSaveHooks, useFetchImageWithAuth, useFetchWithAuth, useGetConfigLayer, useGlobalContext, useHeaderRender, useHideIfEmptyDataSource, useLayerHiddenAttributes, useLayerParams, useMapContext, useMapDraw, useMapImages, useMaxZoomTo, useProjectDashboardInit, usePythonSandbox, usePythonTask, useRedrawLayer, useRelatedDataSourceAttributes, useRemoteTask, useRenderElement, useResizeBox, useResizeDrag, useSavePrototypeBuilder, useServerNotificationsContext, useShownOtherItems, useToggle, useUpdateDataSource, useVisibleProjectItems, useWidgetConfig, useWidgetContext, useWidgetFilters, useWidgetPage, useWindowResize, useWrapperSize, useZoomToFeatures, useZoomToPoint, withTrackSize };
20937
21256
  //# sourceMappingURL=react.esm.js.map