@evergis/react 4.0.159 → 4.0.162
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Dashboard/componentTypes.d.ts +6 -6
- package/dist/components/Dashboard/constants.d.ts +25 -3
- package/dist/components/Dashboard/containers/FiltersContainer/utils/getFilterWidth.d.ts +2 -3
- package/dist/components/Dashboard/elements/ElementControl/hooks/useFirstNonEmptyValue.d.ts +6 -0
- package/dist/components/Dashboard/elements/ElementModal/hooks/useModalContent.d.ts +2 -2
- package/dist/components/Dashboard/elements/ElementTable/utils/tableBox.d.ts +2 -2
- package/dist/components/Dashboard/hooks/index.d.ts +2 -0
- package/dist/components/Dashboard/hooks/useConfigStringSources.d.ts +8 -0
- package/dist/components/Dashboard/hooks/useDashboardFilterStore.d.ts +9 -0
- package/dist/components/Dashboard/hooks/useEqualTileWidth.d.ts +1 -1
- package/dist/components/Dashboard/hooks/useWidgetPage.d.ts +3 -0
- package/dist/components/Dashboard/hooks/useWrapperSize.d.ts +3 -3
- package/dist/components/Dashboard/styled/containers.d.ts +5 -6
- package/dist/components/Dashboard/styled/index.d.ts +2 -3
- package/dist/components/Dashboard/types.d.ts +33 -2
- package/dist/components/Dashboard/utils/applyQueryFilters.d.ts +4 -3
- package/dist/components/Dashboard/utils/chartAxis.d.ts +1 -1
- package/dist/components/Dashboard/utils/configString/formatConditionString.d.ts +18 -0
- package/dist/components/Dashboard/utils/configString/formatters.d.ts +16 -0
- package/dist/components/Dashboard/utils/configString/getFilterTokenValue.d.ts +13 -0
- package/dist/components/Dashboard/utils/configString/index.d.ts +10 -0
- package/dist/components/Dashboard/utils/configString/isDashboardReference.d.ts +2 -0
- package/dist/components/Dashboard/utils/configString/referencesFilters.d.ts +11 -0
- package/dist/components/Dashboard/utils/configString/resolveConfigToken.d.ts +3 -0
- package/dist/components/Dashboard/utils/configString/resolveElementStrings.d.ts +8 -0
- package/dist/components/Dashboard/utils/configString/resolveFilterDefaults.d.ts +8 -0
- package/dist/components/Dashboard/utils/configString/tokenizeConfigString.d.ts +5 -0
- package/dist/components/Dashboard/utils/configString/types.d.ts +78 -0
- package/dist/components/Dashboard/utils/formatDataSourceCondition.d.ts +16 -23
- package/dist/components/Dashboard/utils/getChangedFilterNames.d.ts +8 -0
- package/dist/components/Dashboard/utils/getPageFilters.d.ts +3 -0
- package/dist/components/Dashboard/utils/getRenderElement.d.ts +1 -1
- package/dist/components/Dashboard/utils/index.d.ts +4 -0
- package/dist/components/Dashboard/utils/selectUpdatingDataSources.d.ts +21 -0
- package/dist/index.js +900 -559
- package/dist/index.js.map +1 -1
- package/dist/react.esm.js +879 -560
- package/dist/react.esm.js.map +1 -1
- package/package.json +11 -4
package/dist/index.js
CHANGED
|
@@ -14,9 +14,9 @@ var wkt = require('wkt');
|
|
|
14
14
|
var turf = require('@turf/turf');
|
|
15
15
|
var uuid = require('uuid');
|
|
16
16
|
var signalr = require('@microsoft/signalr');
|
|
17
|
+
var findAnd = require('find-and');
|
|
17
18
|
var MapboxDraw = require('@mapbox/mapbox-gl-draw');
|
|
18
19
|
var react = require('swiper/react');
|
|
19
|
-
var findAnd = require('find-and');
|
|
20
20
|
var ReactMarkdown = require('react-markdown');
|
|
21
21
|
var rehypeRaw = require('rehype-raw');
|
|
22
22
|
var rehypeSanitize = require('rehype-sanitize');
|
|
@@ -2104,7 +2104,7 @@ function tweenValue(transition, name, value) {
|
|
|
2104
2104
|
};
|
|
2105
2105
|
}
|
|
2106
2106
|
|
|
2107
|
-
function interpolate(a, b) {
|
|
2107
|
+
function interpolate$1(a, b) {
|
|
2108
2108
|
var c;
|
|
2109
2109
|
return (typeof b === "number" ? interpolateNumber
|
|
2110
2110
|
: b instanceof color ? interpolateRgb
|
|
@@ -2179,7 +2179,7 @@ function attrFunctionNS(fullname, interpolate, value) {
|
|
|
2179
2179
|
}
|
|
2180
2180
|
|
|
2181
2181
|
function transition_attr(name, value) {
|
|
2182
|
-
var fullname = namespace(name), i = fullname === "transform" ? interpolateTransformSvg : interpolate;
|
|
2182
|
+
var fullname = namespace(name), i = fullname === "transform" ? interpolateTransformSvg : interpolate$1;
|
|
2183
2183
|
return this.attrTween(name, typeof value === "function"
|
|
2184
2184
|
? (fullname.local ? attrFunctionNS : attrFunction)(fullname, i, tweenValue(this, "attr." + name, value))
|
|
2185
2185
|
: value == null ? (fullname.local ? attrRemoveNS : attrRemove)(fullname)
|
|
@@ -2487,7 +2487,7 @@ function styleMaybeRemove(id, name) {
|
|
|
2487
2487
|
}
|
|
2488
2488
|
|
|
2489
2489
|
function transition_style(name, value, priority) {
|
|
2490
|
-
var i = (name += "") === "transform" ? interpolateTransformCss : interpolate;
|
|
2490
|
+
var i = (name += "") === "transform" ? interpolateTransformCss : interpolate$1;
|
|
2491
2491
|
return value == null ? this
|
|
2492
2492
|
.styleTween(name, styleNull(name, i))
|
|
2493
2493
|
.on("end.style." + name, styleRemove(name))
|
|
@@ -3428,9 +3428,42 @@ const PROJECT_PROPS = [PROJECT_ALIAS_PROP, PROJECT_NAME_PROP, ""];
|
|
|
3428
3428
|
const PROVIDER_PREFIX = "$";
|
|
3429
3429
|
exports.ProviderPrefix = void 0;
|
|
3430
3430
|
(function (ProviderPrefix) {
|
|
3431
|
+
/** `$card:<слой>:<поле>` — атрибут объекта карточки. */
|
|
3431
3432
|
ProviderPrefix["Card"] = "card";
|
|
3432
|
-
|
|
3433
|
+
/**
|
|
3434
|
+
* Ближайший дашборд, в ряд к `%` своего виджета: `$dashboard:<фильтр>[.свойство]` — его фильтр,
|
|
3435
|
+
* `$dashboard:<датасорс>:<поле>` — поле первой строки его датасорса.
|
|
3436
|
+
*/
|
|
3437
|
+
ProviderPrefix["Dashboard"] = "dashboard";
|
|
3433
3438
|
})(exports.ProviderPrefix || (exports.ProviderPrefix = {}));
|
|
3439
|
+
/**
|
|
3440
|
+
* Имя слоя или датасорса в подстановке: буквы, цифры, `_`, `.` и `-`. Более широкий класс захватил бы
|
|
3441
|
+
* соседний токен: `$dashboard:a.min–$dashboard:a.max` читался бы как датасорс `a.min–$dashboard`.
|
|
3442
|
+
*/
|
|
3443
|
+
const CONFIG_SOURCE_NAME = "[\\w.-]+";
|
|
3444
|
+
/**
|
|
3445
|
+
* Все подстановки строки конфига одной грамматикой, по группам:
|
|
3446
|
+
* 1–2 `$dashboard:<датасорс>:<поле>`, 3–4 `$dashboard:<фильтр>[.свойство]`, 5–6 `$card:<слой>:<поле>`,
|
|
3447
|
+
* 7–8 `%<фильтр>[.свойство]`, 9 `{<атрибут>}`. Форма датасорса проверяется раньше формы фильтра:
|
|
3448
|
+
* их различает второе двоеточие. Имя фильтра — `\w+` целиком, поэтому `%name` не цепляет начало
|
|
3449
|
+
* `%name2`, а точка в конце предложения («за %year.») не считается свойством.
|
|
3450
|
+
*/
|
|
3451
|
+
const CONFIG_TOKEN_REGEXP = new RegExp([
|
|
3452
|
+
`\\${PROVIDER_PREFIX}${exports.ProviderPrefix.Dashboard}:(${CONFIG_SOURCE_NAME}):(\\w+)`,
|
|
3453
|
+
`\\${PROVIDER_PREFIX}${exports.ProviderPrefix.Dashboard}:(\\w+)(?:\\.(\\w+))?`,
|
|
3454
|
+
`\\${PROVIDER_PREFIX}${exports.ProviderPrefix.Card}:(${CONFIG_SOURCE_NAME}):(\\w+)`,
|
|
3455
|
+
`${FILTER_PREFIX}(\\w+)(?:\\.(\\w+))?`,
|
|
3456
|
+
"\\{([^{}\\s]+)\\}",
|
|
3457
|
+
].join("|"), "g");
|
|
3458
|
+
/** Свойства границ диапазона: `%name.min` / `%name.max`. */
|
|
3459
|
+
const RANGE_PROPS = ["min", "max"];
|
|
3460
|
+
/** Разделитель элементов массива, подставленного в текст UI. */
|
|
3461
|
+
const CONFIG_TEXT_ARRAY_SEPARATOR = ", ";
|
|
3462
|
+
/**
|
|
3463
|
+
* Подписи в `options` элемента, которые проходят через подстановки строк конфига. Список явный:
|
|
3464
|
+
* служебные строки (`attributeName`, `chartId`, `relatedDataSource`, ...) подстановкам не подлежат.
|
|
3465
|
+
*/
|
|
3466
|
+
const CONFIG_STRING_OPTION_KEYS = ["label", "placeholder", "title"];
|
|
3434
3467
|
const FILTERED_VALUE_OPACITY = 28;
|
|
3435
3468
|
const DEFAULT_ATTRIBUTE_NAME = "name";
|
|
3436
3469
|
const DEFAULT_DATA_SOURCE_LIMIT = 100;
|
|
@@ -3485,8 +3518,8 @@ const BASE_CONTAINER_STYLE = {
|
|
|
3485
3518
|
/**
|
|
3486
3519
|
* Маркер тела контейнера внутри `ContainerRoot` — содержимое без заголовка.
|
|
3487
3520
|
*
|
|
3488
|
-
* Нужен, чтобы находить тело по DOM снаружи: `#<id контейнера>`
|
|
3489
|
-
*
|
|
3521
|
+
* Нужен, чтобы находить тело по DOM снаружи: `#<id контейнера>` указывает на корень, то есть на
|
|
3522
|
+
* пару «заголовок + тело».
|
|
3490
3523
|
*/
|
|
3491
3524
|
const CONTAINER_BODY_ATTRIBUTE = "data-container-body";
|
|
3492
3525
|
/**
|
|
@@ -3873,6 +3906,8 @@ const addDataSources = (dashboardConfiguration, pageIndex, layerNames) => {
|
|
|
3873
3906
|
return newConfig.children;
|
|
3874
3907
|
};
|
|
3875
3908
|
|
|
3909
|
+
const isEmptyValue = (value) => value === "" || value === null || value === undefined;
|
|
3910
|
+
|
|
3876
3911
|
const transparentizeColor = (originalColor, percent) => {
|
|
3877
3912
|
const color = new color$1.Color(originalColor);
|
|
3878
3913
|
color.a = Math.round((255 / 100) * percent);
|
|
@@ -4212,7 +4247,7 @@ const formatDateValue = (stringFormat, dateValue) => {
|
|
|
4212
4247
|
const timeOption = getValueFromOptions(timeOptions, stringFormat.format);
|
|
4213
4248
|
const timeOptionValue = timeOption && (isRuLocale ? timeOption.value : `${timeOption.value} aa`);
|
|
4214
4249
|
const timeFormat = (timeOptionValue && dateFns.format(value, showTimeZone ? `${timeOptionValue} zz` : timeOptionValue)) || "";
|
|
4215
|
-
return dateFormat ?
|
|
4250
|
+
return dateFormat ? [dateFormat, timeFormat].filter(Boolean).join(" ") : "";
|
|
4216
4251
|
};
|
|
4217
4252
|
const appendUnitsLabel = (value, unitsLabel, noUnits) => (unitsLabel && !noUnits ? `${value} ${unitsLabel}` : value)?.toString() || "";
|
|
4218
4253
|
const formatNumberValue = (stringFormat, value, type, noUnits = false) => {
|
|
@@ -4233,8 +4268,8 @@ const formatNumberValue = (stringFormat, value, type, noUnits = false) => {
|
|
|
4233
4268
|
if (typeof compactValue === "string" && compactValue) {
|
|
4234
4269
|
return appendUnitsLabel(compactValue, unitsLabel, noUnits);
|
|
4235
4270
|
}
|
|
4236
|
-
if (hasRounding && (!isIntValue || !isDefaultScaling)) {
|
|
4237
|
-
currentValue =
|
|
4271
|
+
if (hasRounding && (!isIntValue || !isDefaultScaling) && !lodash.isNil(currentValue)) {
|
|
4272
|
+
currentValue = Number(currentValue).toFixed(rounding);
|
|
4238
4273
|
}
|
|
4239
4274
|
if (splitDigitGroup) {
|
|
4240
4275
|
currentValue = numberWithSpaces(currentValue);
|
|
@@ -4355,6 +4390,49 @@ const metersPerPixel = (zoom, lat) => {
|
|
|
4355
4390
|
return (156543.03 * Math.cos(lat * (Math.PI / 180))) / 2 ** zoom;
|
|
4356
4391
|
};
|
|
4357
4392
|
|
|
4393
|
+
const checkIsDateFormat = (value) => value.startsWith("#'") && value.endsWith("'");
|
|
4394
|
+
const checkIfNeedQuotes = (value, attributeType) => {
|
|
4395
|
+
return attributeType
|
|
4396
|
+
? !NUMERIC_ATTRIBUTE_TYPES.includes(attributeType)
|
|
4397
|
+
: typeof value === "number"
|
|
4398
|
+
? false
|
|
4399
|
+
: typeof value === "string" && !isNumeric(value) && !checkIsDateFormat(value);
|
|
4400
|
+
};
|
|
4401
|
+
const formatConditionValue = ({ value, attributeType, defaultValue, checkQuotes = true, isSetParams = false, }) => {
|
|
4402
|
+
if (isEmptyValue(value))
|
|
4403
|
+
return "";
|
|
4404
|
+
const formattedValue = Array.isArray(defaultValue) && !Array.isArray(value) ? [value] : value;
|
|
4405
|
+
const isArray = Array.isArray(formattedValue);
|
|
4406
|
+
let stringValue = formattedValue.toString();
|
|
4407
|
+
if (isArray) {
|
|
4408
|
+
return formattedValue.length && !formattedValue.every(item => !item)
|
|
4409
|
+
? `[${formattedValue
|
|
4410
|
+
.map(item => (checkQuotes && checkIfNeedQuotes(item, attributeType) ? `'${item}'` : item))
|
|
4411
|
+
.join(",")}]`
|
|
4412
|
+
: "";
|
|
4413
|
+
}
|
|
4414
|
+
if (isSetParams && checkIsDateFormat(stringValue)) {
|
|
4415
|
+
stringValue = stringValue.slice(2, -1);
|
|
4416
|
+
}
|
|
4417
|
+
return checkQuotes && checkIfNeedQuotes(stringValue, attributeType) ? `'${stringValue}'` : stringValue;
|
|
4418
|
+
};
|
|
4419
|
+
|
|
4420
|
+
/**
|
|
4421
|
+
* Значение системной подстановки текущего проекта. Голый `%project` и `%project.name` дают
|
|
4422
|
+
* системное имя, `%project.alias` — алиас с фолбэком на имя: пустой алиас не должен уводить
|
|
4423
|
+
* запрос в пустое значение. Чужое свойство (`%project.foo`) не наше — вернём `undefined`,
|
|
4424
|
+
* и плейсхолдер останется нетронутым, как у незаполненного фильтра.
|
|
4425
|
+
*/
|
|
4426
|
+
const getProjectValue = ({ prop, projectName, projectAlias, }) => {
|
|
4427
|
+
if (prop && prop !== PROJECT_NAME_PROP && prop !== PROJECT_ALIAS_PROP) {
|
|
4428
|
+
return undefined;
|
|
4429
|
+
}
|
|
4430
|
+
if (prop === PROJECT_ALIAS_PROP) {
|
|
4431
|
+
return projectAlias || projectName || undefined;
|
|
4432
|
+
}
|
|
4433
|
+
return projectName || undefined;
|
|
4434
|
+
};
|
|
4435
|
+
|
|
4358
4436
|
/** Ключ уровня в значении tree-фильтра: `l1`, `l2`, ... */
|
|
4359
4437
|
const LEVEL_KEY = /^l\d+$/;
|
|
4360
4438
|
/**
|
|
@@ -4404,22 +4482,6 @@ const getDataSourceFilterValue = ({ filterName, filterProp, attributeAlias, data
|
|
|
4404
4482
|
return feature?.properties?.[filterProp];
|
|
4405
4483
|
};
|
|
4406
4484
|
|
|
4407
|
-
/**
|
|
4408
|
-
* Значение системной подстановки текущего проекта. Голый `%project` и `%project.name` дают
|
|
4409
|
-
* системное имя, `%project.alias` — алиас с фолбэком на имя: пустой алиас не должен уводить
|
|
4410
|
-
* запрос в пустое значение. Чужое свойство (`%project.foo`) не наше — вернём `undefined`,
|
|
4411
|
-
* и плейсхолдер останется нетронутым, как у незаполненного фильтра.
|
|
4412
|
-
*/
|
|
4413
|
-
const getProjectValue = ({ prop, projectName, projectAlias, }) => {
|
|
4414
|
-
if (prop && prop !== PROJECT_NAME_PROP && prop !== PROJECT_ALIAS_PROP) {
|
|
4415
|
-
return undefined;
|
|
4416
|
-
}
|
|
4417
|
-
if (prop === PROJECT_ALIAS_PROP) {
|
|
4418
|
-
return projectAlias || projectName || undefined;
|
|
4419
|
-
}
|
|
4420
|
-
return projectName || undefined;
|
|
4421
|
-
};
|
|
4422
|
-
|
|
4423
4485
|
const getSelectedFilterValue = (filterName, selectedFilters, defaultValue) => {
|
|
4424
4486
|
return ((!lodash.isNil(selectedFilters?.[filterName]?.value) &&
|
|
4425
4487
|
Array.isArray(defaultValue) &&
|
|
@@ -4428,124 +4490,370 @@ const getSelectedFilterValue = (filterName, selectedFilters, defaultValue) => {
|
|
|
4428
4490
|
: selectedFilters?.[filterName]?.value) ?? defaultValue);
|
|
4429
4491
|
};
|
|
4430
4492
|
|
|
4431
|
-
const
|
|
4493
|
+
const known = (value, defaultValue) => ({ known: true, value, defaultValue });
|
|
4494
|
+
const getRangeBound = (prop, defaultValue, scalarDefault) => {
|
|
4495
|
+
if (Array.isArray(defaultValue)) {
|
|
4496
|
+
return defaultValue[prop === "min" ? 0 : 1];
|
|
4497
|
+
}
|
|
4498
|
+
return scalarDefault ? defaultValue : undefined;
|
|
4499
|
+
};
|
|
4500
|
+
/** `.prop` — свойство строки `relatedDataSource`, выбранной фильтром. */
|
|
4501
|
+
const getRelatedValue = ({ name, prop, store, configFilter, defaultValue }) => getDataSourceFilterValue({
|
|
4502
|
+
filterName: name,
|
|
4503
|
+
filterProp: prop,
|
|
4504
|
+
selectedFilters: store.selectedFilters ?? {},
|
|
4505
|
+
attributeAlias: configFilter?.attributeAlias,
|
|
4506
|
+
dataSource: getDataSource(configFilter?.relatedDataSource, store.dataSources),
|
|
4507
|
+
}) ?? defaultValue;
|
|
4508
|
+
const getTreeLevel = (value, prop) => {
|
|
4509
|
+
const level = value[prop];
|
|
4510
|
+
return lodash.isNil(level) || !level.length ? undefined : level;
|
|
4511
|
+
};
|
|
4512
|
+
/** Параметры запроса и текст UI: пустое (`null`/`undefined`) значение падает в дефолт. */
|
|
4513
|
+
const getValueModeToken = (context) => {
|
|
4514
|
+
const { name, prop, store, configFilter, selected, defaultValue } = context;
|
|
4515
|
+
if (RANGE_PROPS.includes(prop)) {
|
|
4516
|
+
return known(selected?.[prop] ?? getRangeBound(prop, defaultValue, true));
|
|
4517
|
+
}
|
|
4518
|
+
if (prop) {
|
|
4519
|
+
return isTreeFilterValue(selected?.value, configFilter)
|
|
4520
|
+
? known(getTreeLevel(selected.value, prop))
|
|
4521
|
+
: known(getRelatedValue(context));
|
|
4522
|
+
}
|
|
4523
|
+
return known(getSelectedFilterValue(name, store.selectedFilters, defaultValue), defaultValue);
|
|
4524
|
+
};
|
|
4525
|
+
/** Условие слоя: выбранное пустое значение не подставляется, объектные значения — только уровнями tree. */
|
|
4526
|
+
const getConditionModeToken = (context) => {
|
|
4527
|
+
const { prop, configFilter, selected, defaultValue } = context;
|
|
4528
|
+
const rawValue = selected !== undefined ? selected.value : defaultValue;
|
|
4529
|
+
if (configFilter?.valueType === "features" || isFeaturesFilterValue(rawValue)) {
|
|
4530
|
+
return { known: false };
|
|
4531
|
+
}
|
|
4532
|
+
if (isTreeFilterValue(rawValue, configFilter)) {
|
|
4533
|
+
return prop ? known(getTreeLevel(rawValue, prop)) : { known: false };
|
|
4534
|
+
}
|
|
4535
|
+
const hasValue = selected !== undefined && !!selected.value?.toString().length;
|
|
4536
|
+
if (RANGE_PROPS.includes(prop)) {
|
|
4537
|
+
return known(hasValue ? selected[prop] : getRangeBound(prop, defaultValue, false));
|
|
4538
|
+
}
|
|
4539
|
+
if (prop) {
|
|
4540
|
+
return known(getRelatedValue(context));
|
|
4541
|
+
}
|
|
4542
|
+
return known(hasValue ? selected.value : selected === undefined ? defaultValue : "", defaultValue);
|
|
4543
|
+
};
|
|
4544
|
+
/**
|
|
4545
|
+
* Значение токена фильтра (`%name[.prop]`, `$dashboard:name[.prop]`) из переданного стора.
|
|
4546
|
+
* Фильтр, о котором стор ничего не знает, — `known: false`: токен остаётся в строке как есть.
|
|
4547
|
+
*/
|
|
4548
|
+
const getFilterTokenValue = (params) => {
|
|
4549
|
+
const { name, store, mode } = params;
|
|
4550
|
+
const configFilter = getConfigFilter(name, store.configFilters);
|
|
4551
|
+
const selected = store.selectedFilters?.[name];
|
|
4552
|
+
if (!configFilter && (mode === "condition" || selected === undefined)) {
|
|
4553
|
+
return { known: false };
|
|
4554
|
+
}
|
|
4555
|
+
const context = { ...params, configFilter, selected, defaultValue: configFilter?.defaultValue };
|
|
4556
|
+
return mode === "condition" ? getConditionModeToken(context) : getValueModeToken(context);
|
|
4557
|
+
};
|
|
4558
|
+
|
|
4559
|
+
const UNKNOWN = { known: false };
|
|
4560
|
+
/** Стор `$dashboard:`. Без отдельного стора строка живёт на самом дашборде — это стор виджета. */
|
|
4561
|
+
const getDashboardStore = ({ dashboard, widget }) => dashboard ?? widget ?? {};
|
|
4562
|
+
const isValidGeometry = (geometry) => !!geometry && !geometry.includes("()") && geometry.endsWith(")");
|
|
4563
|
+
/**
|
|
4564
|
+
* Системные подстановки `%geometry`, `%extent`, `%zoom`, `%project[.name|.alias]` — значения из
|
|
4565
|
+
* `GlobalContext`, а не из фильтров. Пока значения нет, `geometry` / `extent` / `zoom` уступают
|
|
4566
|
+
* одноимённому фильтру; имя `project` зарезервировано целиком.
|
|
4567
|
+
*/
|
|
4568
|
+
const resolveSystemToken = ({ name, prop }, sources) => {
|
|
4569
|
+
const { geometry, extent, zoomLevel, projectName, projectAlias } = sources;
|
|
4570
|
+
if (name === GEOMETRY_FILTER_NAME && !prop && isValidGeometry(geometry)) {
|
|
4571
|
+
return { known: true, value: geometry, system: "quoted" };
|
|
4572
|
+
}
|
|
4573
|
+
if (name === EXTENT_FILTER_NAME && !prop && extent) {
|
|
4574
|
+
return { known: true, value: extent, system: "quoted" };
|
|
4575
|
+
}
|
|
4576
|
+
if (name === ZOOM_FILTER_NAME && !prop && !lodash.isNil(zoomLevel)) {
|
|
4577
|
+
return { known: true, value: zoomLevel, system: "number" };
|
|
4578
|
+
}
|
|
4579
|
+
if (name === PROJECT_FILTER_NAME) {
|
|
4580
|
+
const value = getProjectValue({ prop, projectName, projectAlias });
|
|
4581
|
+
return lodash.isNil(value) ? UNKNOWN : { known: true, value, system: "quoted" };
|
|
4582
|
+
}
|
|
4583
|
+
return null;
|
|
4584
|
+
};
|
|
4585
|
+
const resolveFilterToken = (token, sources, mode) => {
|
|
4586
|
+
const { scope, name, prop } = token;
|
|
4587
|
+
if (scope === "widget") {
|
|
4588
|
+
const system = resolveSystemToken(token, sources);
|
|
4589
|
+
if (system)
|
|
4590
|
+
return system;
|
|
4591
|
+
}
|
|
4592
|
+
const store = scope === "dashboard" ? getDashboardStore(sources) : sources.widget ?? {};
|
|
4593
|
+
return getFilterTokenValue({ name, prop, store, mode });
|
|
4594
|
+
};
|
|
4595
|
+
/** `$card:<слой>:<поле>` — атрибут объекта карточки, если карточка открыта на этом слое. */
|
|
4596
|
+
const resolveCardToken = ({ layerName, fieldName }, { attributes, layerInfo }) => {
|
|
4597
|
+
if (!attributes || layerInfo?.name !== layerName)
|
|
4598
|
+
return UNKNOWN;
|
|
4599
|
+
const attribute = attributes.find(({ attributeName }) => attributeName === fieldName);
|
|
4600
|
+
return { known: true, value: attribute?.value ?? null, attributeType: attribute?.type };
|
|
4601
|
+
};
|
|
4602
|
+
/**
|
|
4603
|
+
* `$dashboard:<датасорс>:<поле>` — поле первой строки датасорса дашборда (по имени, затем по
|
|
4604
|
+
* `layerName`). Пока датасорс не загружен, значение пустое: токен не должен мелькать в тексте.
|
|
4605
|
+
*/
|
|
4606
|
+
const resolveDataSourceToken = ({ dataSourceName, fieldName }, sources) => {
|
|
4607
|
+
const { dataSources } = getDashboardStore(sources);
|
|
4608
|
+
const dataSource = dataSources?.find(({ name }) => name === dataSourceName) ??
|
|
4609
|
+
dataSources?.find(({ layerName }) => layerName === dataSourceName);
|
|
4610
|
+
return { known: true, value: dataSource?.features?.[0]?.properties?.[fieldName] ?? null };
|
|
4611
|
+
};
|
|
4612
|
+
/** `{name}` — атрибут объекта; пустой атрибут даёт пустую строку, незнакомое имя остаётся как есть. */
|
|
4613
|
+
const resolveAttributeToken = ({ name }, { attributes }) => {
|
|
4614
|
+
const attribute = attributes?.find(({ attributeName }) => attributeName === name);
|
|
4615
|
+
return attribute ? { known: true, value: attribute.value ?? "", attributeType: attribute.type } : UNKNOWN;
|
|
4616
|
+
};
|
|
4617
|
+
/** Сырое значение токена строки конфига — без форматирования под параметр, условие или текст. */
|
|
4618
|
+
const resolveConfigToken = (token, sources, mode) => {
|
|
4619
|
+
if (token.kind === "filter")
|
|
4620
|
+
return resolveFilterToken(token, sources, mode);
|
|
4621
|
+
if (token.kind === "card")
|
|
4622
|
+
return resolveCardToken(token, sources);
|
|
4623
|
+
if (token.kind === "dataSource")
|
|
4624
|
+
return resolveDataSourceToken(token, sources);
|
|
4625
|
+
return resolveAttributeToken(token, sources);
|
|
4626
|
+
};
|
|
4627
|
+
|
|
4628
|
+
const toToken = ([raw, ...groups]) => {
|
|
4629
|
+
const [dataSourceName, dataSourceField, dashboardFilter, dashboardProp, cardLayer, cardField, filterName, filterProp, attributeName,] = groups;
|
|
4630
|
+
if (dataSourceName) {
|
|
4631
|
+
return { kind: "dataSource", dataSourceName, fieldName: dataSourceField, raw };
|
|
4632
|
+
}
|
|
4633
|
+
if (dashboardFilter) {
|
|
4634
|
+
return { kind: "filter", scope: "dashboard", name: dashboardFilter, prop: dashboardProp, raw };
|
|
4635
|
+
}
|
|
4636
|
+
if (cardLayer) {
|
|
4637
|
+
return { kind: "card", layerName: cardLayer, fieldName: cardField, raw };
|
|
4638
|
+
}
|
|
4639
|
+
if (filterName) {
|
|
4640
|
+
return { kind: "filter", scope: "widget", name: filterName, prop: filterProp, raw };
|
|
4641
|
+
}
|
|
4642
|
+
return { kind: "attribute", name: attributeName, raw };
|
|
4643
|
+
};
|
|
4644
|
+
/** Разбирает строку конфига на литералы и токены подстановок — в исходном порядке. */
|
|
4645
|
+
const tokenizeConfigString = (value) => {
|
|
4646
|
+
const segments = [];
|
|
4647
|
+
let lastIndex = 0;
|
|
4648
|
+
for (const match of value.matchAll(CONFIG_TOKEN_REGEXP)) {
|
|
4649
|
+
if (match.index > lastIndex) {
|
|
4650
|
+
segments.push(value.slice(lastIndex, match.index));
|
|
4651
|
+
}
|
|
4652
|
+
segments.push(toToken(match));
|
|
4653
|
+
lastIndex = match.index + match[0].length;
|
|
4654
|
+
}
|
|
4655
|
+
if (lastIndex < value.length) {
|
|
4656
|
+
segments.push(value.slice(lastIndex));
|
|
4657
|
+
}
|
|
4658
|
+
return segments;
|
|
4659
|
+
};
|
|
4660
|
+
/** Строка целиком — один токен: тогда значение уходит как есть, с типом (число, массив). */
|
|
4661
|
+
const getSingleToken = (segments) => segments.length === 1 && typeof segments[0] !== "string" ? segments[0] : null;
|
|
4662
|
+
|
|
4663
|
+
/** Склейка сегментов: токен заменяется своим форматом, незнакомый — остаётся как написан. */
|
|
4664
|
+
const joinConfigSegments = (segments, formatToken) => segments.map(segment => (typeof segment === "string" ? segment : formatToken(segment))).join("");
|
|
4665
|
+
const hasTokens = (segments) => segments.some(segment => typeof segment !== "string");
|
|
4666
|
+
const toParameterText = (value) => (lodash.isNil(value) ? "" : String(value));
|
|
4667
|
+
const toUiText = (value) => {
|
|
4668
|
+
if (lodash.isNil(value) || isObject(value) && !(value instanceof Date))
|
|
4669
|
+
return "";
|
|
4670
|
+
if (Array.isArray(value)) {
|
|
4671
|
+
return value
|
|
4672
|
+
.map(toUiText)
|
|
4673
|
+
.filter(item => item !== "")
|
|
4674
|
+
.join(CONFIG_TEXT_ARRAY_SEPARATOR);
|
|
4675
|
+
}
|
|
4676
|
+
return String(value);
|
|
4677
|
+
};
|
|
4678
|
+
const interpolate = (segments, sources, toText) => joinConfigSegments(segments, token => {
|
|
4679
|
+
const resolved = resolveConfigToken(token, sources, "value");
|
|
4680
|
+
return resolved.known ? toText(resolved.value) : token.raw;
|
|
4681
|
+
});
|
|
4682
|
+
/**
|
|
4683
|
+
* Значение параметра запроса. Строка-токен целиком уходит сырым значением с типом, а пустой или
|
|
4684
|
+
* незнакомый фильтр не уходит вовсе (`undefined`). Токены внутри строки интерполируются текстом.
|
|
4685
|
+
*/
|
|
4686
|
+
const resolveQueryParameter = (value, sources) => {
|
|
4687
|
+
if (typeof value !== "string")
|
|
4688
|
+
return value;
|
|
4689
|
+
const segments = tokenizeConfigString(value);
|
|
4690
|
+
const token = getSingleToken(segments);
|
|
4691
|
+
if (token) {
|
|
4692
|
+
const resolved = resolveConfigToken(token, sources, "value");
|
|
4693
|
+
// Незнакомое имя в фигурных скобках — не подстановка, а литерал (JSON, шаблон сервиса).
|
|
4694
|
+
if (!resolved.known && token.kind === "attribute")
|
|
4695
|
+
return value;
|
|
4696
|
+
return resolved.known && !lodash.isNil(resolved.value) ? resolved.value : undefined;
|
|
4697
|
+
}
|
|
4698
|
+
return hasTokens(segments) ? interpolate(segments, sources, toParameterText) : value;
|
|
4699
|
+
};
|
|
4700
|
+
/**
|
|
4701
|
+
* Строка конфига для UI (значение элемента, дефолт, подпись). Строка-токен целиком отдаёт сырое
|
|
4702
|
+
* значение (число, массив — например, дефолт мультиселекта); пустое — пустая строка. Внутри текста
|
|
4703
|
+
* массив склеивается через `", "`. Незнакомый токен (опечатка, литерал `50%off`) остаётся как написан.
|
|
4704
|
+
*/
|
|
4705
|
+
const resolveConfigString = (value, sources) => {
|
|
4706
|
+
if (typeof value !== "string" || !value)
|
|
4707
|
+
return value;
|
|
4708
|
+
const segments = tokenizeConfigString(value);
|
|
4709
|
+
const token = getSingleToken(segments);
|
|
4710
|
+
if (token) {
|
|
4711
|
+
const resolved = resolveConfigToken(token, sources, "value");
|
|
4712
|
+
if (!resolved.known)
|
|
4713
|
+
return value;
|
|
4714
|
+
return lodash.isNil(resolved.value) || isObject(resolved.value) && !(resolved.value instanceof Date) ? "" : resolved.value;
|
|
4715
|
+
}
|
|
4716
|
+
return hasTokens(segments) ? interpolate(segments, sources, toUiText) : value;
|
|
4717
|
+
};
|
|
4718
|
+
|
|
4719
|
+
const SET_PARAMS_REGEXP = /\$\([^)]+\)/g;
|
|
4720
|
+
const CONDITION_SPLITTER = " AND ";
|
|
4721
|
+
const formatResolvedToken = (resolved, isSetParams) => {
|
|
4722
|
+
if (!resolved.known)
|
|
4723
|
+
return "";
|
|
4724
|
+
const { value, system, attributeType, defaultValue } = resolved;
|
|
4725
|
+
if (system === "quoted")
|
|
4726
|
+
return `'${value}'`;
|
|
4727
|
+
if (system === "number")
|
|
4728
|
+
return String(value);
|
|
4729
|
+
return formatConditionValue({
|
|
4730
|
+
value: (attributeType ? value?.toString() : value),
|
|
4731
|
+
attributeType,
|
|
4732
|
+
defaultValue,
|
|
4733
|
+
isSetParams,
|
|
4734
|
+
});
|
|
4735
|
+
};
|
|
4736
|
+
/**
|
|
4737
|
+
* Eql-параметры слоя подставляются по голому имени. Имя внутри `$(имя=` прячется на время замены,
|
|
4738
|
+
* чтобы не задеть объявление set-параметра.
|
|
4739
|
+
*/
|
|
4740
|
+
const applyEqlParameters = (section, { eqlParameters, layerParams }) => {
|
|
4741
|
+
if (!eqlParameters || !layerParams)
|
|
4742
|
+
return section;
|
|
4743
|
+
return Object.keys(eqlParameters).reduce((result, param) => result
|
|
4744
|
+
.replace(new RegExp(`\\$\\(${param}=`), "***")
|
|
4745
|
+
.replace(new RegExp(param), formatConditionValue({ value: layerParams[param] }))
|
|
4746
|
+
.replace(/\*\*\*/, `$(${param}=`), section);
|
|
4747
|
+
};
|
|
4748
|
+
/**
|
|
4749
|
+
* Одна секция условия: все токены в формате условия. Пустое значение фильтра оставляет токен как
|
|
4750
|
+
* есть; пустой атрибут объекта (`{name}`) заменяется пустой строкой.
|
|
4751
|
+
*/
|
|
4752
|
+
const formatConditionSection = (section, sources, { isSetParams = false, ...eql } = {}) => {
|
|
4753
|
+
const formatted = joinConfigSegments(tokenizeConfigString(section), (token) => {
|
|
4754
|
+
const resolved = resolveConfigToken(token, sources, "condition");
|
|
4755
|
+
const value = formatResolvedToken(resolved, isSetParams);
|
|
4756
|
+
return value || (token.kind === "attribute" && resolved.known ? "" : token.raw);
|
|
4757
|
+
});
|
|
4758
|
+
return applyEqlParameters(formatted, eql);
|
|
4759
|
+
};
|
|
4760
|
+
/**
|
|
4761
|
+
* Условие слоя: ведущие set-параметры `$(имя=…)` форматируются отдельно (даты без `#'…'`),
|
|
4762
|
+
* остаток — по секциям `AND`.
|
|
4763
|
+
*/
|
|
4764
|
+
const formatConditionString = (condition, sources, eql = {}) => {
|
|
4765
|
+
const setParams = condition.match(SET_PARAMS_REGEXP) ?? [];
|
|
4766
|
+
const setParamsSection = setParams.map(section => formatConditionSection(section, sources, { ...eql, isSetParams: true }));
|
|
4767
|
+
const conditionSection = condition
|
|
4768
|
+
.substring(setParams.join("").length)
|
|
4769
|
+
.split(CONDITION_SPLITTER)
|
|
4770
|
+
.map(section => formatConditionSection(section, sources, eql));
|
|
4771
|
+
if (!setParamsSection.length)
|
|
4772
|
+
return conditionSection.join(CONDITION_SPLITTER);
|
|
4773
|
+
return conditionSection.length
|
|
4774
|
+
? [setParamsSection.join(""), conditionSection.join(CONDITION_SPLITTER)].join(" ")
|
|
4775
|
+
: setParamsSection.join("");
|
|
4776
|
+
};
|
|
4777
|
+
|
|
4778
|
+
const DASHBOARD_TOKEN_PREFIX = `${PROVIDER_PREFIX}${exports.ProviderPrefix.Dashboard}:`;
|
|
4779
|
+
/** Строка ссылается на фильтр дашборда (`$dashboard:`). */
|
|
4780
|
+
const isDashboardReference = (value) => typeof value === "string" && value.includes(DASHBOARD_TOKEN_PREFIX);
|
|
4781
|
+
|
|
4782
|
+
/**
|
|
4783
|
+
* Строка конфига ссылается на один из фильтров: `%name[.prop]` — на фильтр виджета,
|
|
4784
|
+
* `$dashboard:name[.prop]` — на фильтр дашборда. На самом дашборде (`hasDashboardStore = false`)
|
|
4785
|
+
* `$dashboard:` — те же фильтры виджета.
|
|
4786
|
+
*/
|
|
4787
|
+
const referencesFilters = ({ value, widgetNames, dashboardNames, hasDashboardStore, }) => typeof value === "string" &&
|
|
4788
|
+
tokenizeConfigString(value).some(segment => {
|
|
4789
|
+
if (typeof segment === "string" || segment.kind !== "filter")
|
|
4790
|
+
return false;
|
|
4791
|
+
const names = segment.scope === "dashboard" && hasDashboardStore ? dashboardNames : widgetNames;
|
|
4792
|
+
return names.includes(segment.name);
|
|
4793
|
+
});
|
|
4794
|
+
|
|
4795
|
+
/**
|
|
4796
|
+
* Узел конфига с подставленными строками: `value`, `defaultValue` и подписи из
|
|
4797
|
+
* {@link CONFIG_STRING_OPTION_KEYS}. Без подстановок узел возвращается тем же объектом —
|
|
4798
|
+
* мемоизированные контейнеры не перерисовываются зря.
|
|
4799
|
+
*/
|
|
4800
|
+
const resolveElementStrings = (element, sources) => {
|
|
4801
|
+
if (!element || !sources)
|
|
4802
|
+
return element;
|
|
4803
|
+
const value = resolveConfigString(element.value, sources);
|
|
4804
|
+
const defaultValue = resolveConfigString(element.defaultValue, sources);
|
|
4805
|
+
const changedOptions = CONFIG_STRING_OPTION_KEYS.reduce((result, key) => {
|
|
4806
|
+
const option = element.options?.[key];
|
|
4807
|
+
const resolved = resolveConfigString(option, sources);
|
|
4808
|
+
return resolved === option ? result : { ...result, [key]: resolved };
|
|
4809
|
+
}, {});
|
|
4810
|
+
const hasChangedOptions = Object.keys(changedOptions).length > 0;
|
|
4811
|
+
if (value === element.value && defaultValue === element.defaultValue && !hasChangedOptions) {
|
|
4812
|
+
return element;
|
|
4813
|
+
}
|
|
4814
|
+
return {
|
|
4815
|
+
...element,
|
|
4816
|
+
value,
|
|
4817
|
+
defaultValue,
|
|
4818
|
+
...(hasChangedOptions && { options: { ...element.options, ...changedOptions } }),
|
|
4819
|
+
};
|
|
4820
|
+
};
|
|
4821
|
+
|
|
4822
|
+
/**
|
|
4823
|
+
* Дефолты фильтров виджета из фильтров дашборда: `defaultValue: "$dashboard:year"`. Резолвится
|
|
4824
|
+
* только `$dashboard:` — ссылки на собственные фильтры (`%name`) дали бы цикл и остаются как есть.
|
|
4825
|
+
* Дефолт живой: пока фильтр виджета не выбран, он следует за дашбордом, ручной выбор его перекрывает.
|
|
4826
|
+
*/
|
|
4827
|
+
const resolveFilterDefaults = (configFilters, dashboard) => {
|
|
4828
|
+
if (!dashboard || !configFilters?.some(({ defaultValue }) => isDashboardReference(defaultValue))) {
|
|
4829
|
+
return configFilters;
|
|
4830
|
+
}
|
|
4831
|
+
return configFilters.map(filter => {
|
|
4832
|
+
if (!isDashboardReference(filter.defaultValue))
|
|
4833
|
+
return filter;
|
|
4834
|
+
const defaultValue = resolveQueryParameter(filter.defaultValue, { widget: {}, dashboard });
|
|
4835
|
+
return defaultValue === filter.defaultValue ? filter : { ...filter, defaultValue: defaultValue };
|
|
4836
|
+
});
|
|
4837
|
+
};
|
|
4838
|
+
|
|
4839
|
+
const applyQueryFilters = ({ parameters: configParameters, filters: configFilters, selectedFilters, dashboard, geometry, extent, zoomLevel, projectName, projectAlias, attributes, layerInfo, dataSources, }) => {
|
|
4432
4840
|
if (!configParameters) {
|
|
4433
4841
|
return {};
|
|
4434
4842
|
}
|
|
4843
|
+
const sources = {
|
|
4844
|
+
widget: { selectedFilters, configFilters, dataSources },
|
|
4845
|
+
dashboard,
|
|
4846
|
+
geometry,
|
|
4847
|
+
extent,
|
|
4848
|
+
zoomLevel,
|
|
4849
|
+
projectName,
|
|
4850
|
+
projectAlias,
|
|
4851
|
+
attributes,
|
|
4852
|
+
layerInfo,
|
|
4853
|
+
};
|
|
4435
4854
|
return Object.keys(configParameters).reduce((result, key) => {
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
const [provider, layerName, fieldName] = raw.split(":");
|
|
4439
|
-
if (provider === exports.ProviderPrefix.Card && fieldName && layerInfo.name === layerName && attributes) {
|
|
4440
|
-
const attribute = attributes.find(({ attributeName }) => attributeName === fieldName);
|
|
4441
|
-
const cardValue = attribute?.value ?? null;
|
|
4442
|
-
if (lodash.isNil(cardValue))
|
|
4443
|
-
return result;
|
|
4444
|
-
return { ...result, [key]: cardValue };
|
|
4445
|
-
}
|
|
4446
|
-
if (provider === exports.ProviderPrefix.Left && layerName && fieldName && projectDataSources?.length) {
|
|
4447
|
-
const dataSource = projectDataSources.find(({ layerName: name }) => name === layerName);
|
|
4448
|
-
const leftValue = dataSource?.features?.[0]?.properties?.[fieldName] ?? null;
|
|
4449
|
-
if (lodash.isNil(leftValue))
|
|
4450
|
-
return result;
|
|
4451
|
-
return { ...result, [key]: leftValue };
|
|
4452
|
-
}
|
|
4453
|
-
return result;
|
|
4454
|
-
}
|
|
4455
|
-
if (typeof configParameters[key] === "string" && configParameters[key].includes("{") && attributes?.length) {
|
|
4456
|
-
const exactAttr = attributes.find(({ attributeName }) => configParameters[key] === `{${attributeName}}`);
|
|
4457
|
-
if (exactAttr) {
|
|
4458
|
-
return { ...result, [key]: exactAttr.value ?? "" };
|
|
4459
|
-
}
|
|
4460
|
-
let interpolated = configParameters[key];
|
|
4461
|
-
attributes.forEach(({ attributeName, value: attrValue }) => {
|
|
4462
|
-
interpolated = interpolated.replace(new RegExp(`\\{${attributeName}\\}`, "g"), attrValue?.toString() ?? "");
|
|
4463
|
-
});
|
|
4464
|
-
return { ...result, [key]: interpolated };
|
|
4465
|
-
}
|
|
4466
|
-
if (typeof configParameters[key] !== "string" || !configParameters[key].startsWith("%")) {
|
|
4467
|
-
return {
|
|
4468
|
-
...result,
|
|
4469
|
-
[key]: configParameters[key],
|
|
4470
|
-
};
|
|
4471
|
-
}
|
|
4472
|
-
const filterFullName = configParameters[key].replace("%", "");
|
|
4473
|
-
const [filterName, filterProp] = filterFullName.includes(".") ? filterFullName.split(".") : [filterFullName, null];
|
|
4474
|
-
const configFilter = getConfigFilter(filterName, configFilters);
|
|
4475
|
-
const { defaultValue, relatedDataSource, attributeAlias } = configFilter || {};
|
|
4476
|
-
if (filterName === GEOMETRY_FILTER_NAME && geometry && !geometry.includes("()") && geometry.endsWith(")")) {
|
|
4477
|
-
return {
|
|
4478
|
-
...result,
|
|
4479
|
-
[key]: geometry,
|
|
4480
|
-
};
|
|
4481
|
-
}
|
|
4482
|
-
// Системные фильтры вида карты: значение приходит из `GlobalContext`, а не из `SelectedFilters`,
|
|
4483
|
-
// поэтому резолвятся до веток `.min` / `.max` / `.property` — как `geometry` выше.
|
|
4484
|
-
if (filterName === EXTENT_FILTER_NAME && extent) {
|
|
4485
|
-
return {
|
|
4486
|
-
...result,
|
|
4487
|
-
[key]: extent,
|
|
4488
|
-
};
|
|
4489
|
-
}
|
|
4490
|
-
// Единственная нестроковая системная подстановка: уровень зума уходит числом.
|
|
4491
|
-
if (filterName === ZOOM_FILTER_NAME && !lodash.isNil(zoomLevel)) {
|
|
4492
|
-
return {
|
|
4493
|
-
...result,
|
|
4494
|
-
[key]: zoomLevel,
|
|
4495
|
-
};
|
|
4496
|
-
}
|
|
4497
|
-
// Текущий проект: значение приходит из `GlobalContext`, а не из `SelectedFilters`. Резолвим
|
|
4498
|
-
// до веток `.min` / `.max` / `.property`, как экстент и зум, — имя `project` зарезервировано.
|
|
4499
|
-
if (filterName === PROJECT_FILTER_NAME) {
|
|
4500
|
-
const projectValue = getProjectValue({ prop: filterProp, projectName, projectAlias });
|
|
4501
|
-
if (lodash.isNil(projectValue)) {
|
|
4502
|
-
return result;
|
|
4503
|
-
}
|
|
4504
|
-
return {
|
|
4505
|
-
...result,
|
|
4506
|
-
[key]: projectValue,
|
|
4507
|
-
};
|
|
4508
|
-
}
|
|
4509
|
-
if (configParameters[key].endsWith(".max")) {
|
|
4510
|
-
return {
|
|
4511
|
-
...result,
|
|
4512
|
-
[key]: selectedFilters?.[filterName]?.max ?? (Array.isArray(defaultValue) ? defaultValue[1] : defaultValue),
|
|
4513
|
-
};
|
|
4514
|
-
}
|
|
4515
|
-
if (configParameters[key].endsWith(".min")) {
|
|
4516
|
-
return {
|
|
4517
|
-
...result,
|
|
4518
|
-
[key]: selectedFilters?.[filterName]?.min ?? (Array.isArray(defaultValue) ? defaultValue[0] : defaultValue),
|
|
4519
|
-
};
|
|
4520
|
-
}
|
|
4521
|
-
if (configParameters[key].includes(".")) {
|
|
4522
|
-
const selectedValue = selectedFilters?.[filterName]?.value;
|
|
4523
|
-
if (isTreeFilterValue(selectedValue, configFilter)) {
|
|
4524
|
-
const levelValue = filterProp ? selectedValue[filterProp] : undefined;
|
|
4525
|
-
if (lodash.isNil(levelValue) || !levelValue.length) {
|
|
4526
|
-
return result;
|
|
4527
|
-
}
|
|
4528
|
-
return { ...result, [key]: levelValue };
|
|
4529
|
-
}
|
|
4530
|
-
return {
|
|
4531
|
-
...result,
|
|
4532
|
-
[key]: getDataSourceFilterValue({
|
|
4533
|
-
filterName,
|
|
4534
|
-
filterProp,
|
|
4535
|
-
selectedFilters,
|
|
4536
|
-
attributeAlias,
|
|
4537
|
-
dataSource: getDataSource(relatedDataSource, dataSources),
|
|
4538
|
-
}) ?? defaultValue,
|
|
4539
|
-
};
|
|
4540
|
-
}
|
|
4541
|
-
const value = getSelectedFilterValue(filterName, selectedFilters, defaultValue);
|
|
4542
|
-
if (lodash.isNil(value)) {
|
|
4543
|
-
return result;
|
|
4544
|
-
}
|
|
4545
|
-
return {
|
|
4546
|
-
...result,
|
|
4547
|
-
[key]: value,
|
|
4548
|
-
};
|
|
4855
|
+
const value = resolveQueryParameter(configParameters[key], sources);
|
|
4856
|
+
return lodash.isUndefined(value) ? result : { ...result, [key]: value };
|
|
4549
4857
|
}, {});
|
|
4550
4858
|
};
|
|
4551
4859
|
|
|
@@ -4600,35 +4908,6 @@ const createConfigPage = (props) => {
|
|
|
4600
4908
|
};
|
|
4601
4909
|
};
|
|
4602
4910
|
|
|
4603
|
-
const isEmptyValue = (value) => value === "" || value === null || value === undefined;
|
|
4604
|
-
|
|
4605
|
-
const checkIsDateFormat = (value) => value.startsWith("#'") && value.endsWith("'");
|
|
4606
|
-
const checkIfNeedQuotes = (value, attributeType) => {
|
|
4607
|
-
return attributeType
|
|
4608
|
-
? !NUMERIC_ATTRIBUTE_TYPES.includes(attributeType)
|
|
4609
|
-
: typeof value === "number"
|
|
4610
|
-
? false
|
|
4611
|
-
: typeof value === "string" && !isNumeric(value) && !checkIsDateFormat(value);
|
|
4612
|
-
};
|
|
4613
|
-
const formatConditionValue = ({ value, attributeType, defaultValue, checkQuotes = true, isSetParams = false, }) => {
|
|
4614
|
-
if (isEmptyValue(value))
|
|
4615
|
-
return "";
|
|
4616
|
-
const formattedValue = Array.isArray(defaultValue) && !Array.isArray(value) ? [value] : value;
|
|
4617
|
-
const isArray = Array.isArray(formattedValue);
|
|
4618
|
-
let stringValue = formattedValue.toString();
|
|
4619
|
-
if (isArray) {
|
|
4620
|
-
return formattedValue.length && !formattedValue.every(item => !item)
|
|
4621
|
-
? `[${formattedValue
|
|
4622
|
-
.map(item => (checkQuotes && checkIfNeedQuotes(item, attributeType) ? `'${item}'` : item))
|
|
4623
|
-
.join(",")}]`
|
|
4624
|
-
: "";
|
|
4625
|
-
}
|
|
4626
|
-
if (isSetParams && checkIsDateFormat(stringValue)) {
|
|
4627
|
-
stringValue = stringValue.slice(2, -1);
|
|
4628
|
-
}
|
|
4629
|
-
return checkQuotes && checkIfNeedQuotes(stringValue, attributeType) ? `'${stringValue}'` : stringValue;
|
|
4630
|
-
};
|
|
4631
|
-
|
|
4632
4911
|
/**
|
|
4633
4912
|
* Подставляет плейсхолдеры уровней `%name.l{N}` значениями id соответствующего уровня
|
|
4634
4913
|
* (формат `[id1,id2,...]` для оператора IN). Уровни, отсутствующие в значении (или пустые),
|
|
@@ -4676,10 +4955,10 @@ const isDevEnvironment = (() => {
|
|
|
4676
4955
|
}
|
|
4677
4956
|
})();
|
|
4678
4957
|
/**
|
|
4679
|
-
* Один раз на источник сообщает, что конфиг
|
|
4958
|
+
* Один раз на источник сообщает, что конфиг задаёт ось устаревшей плоской формой.
|
|
4680
4959
|
*
|
|
4681
|
-
*
|
|
4682
|
-
*
|
|
4960
|
+
* Предупреждение показывает, какие конфиги ещё держатся на плоской форме, — без него её поддержку
|
|
4961
|
+
* нельзя убрать, не проверив все конфиги вручную.
|
|
4683
4962
|
*/
|
|
4684
4963
|
const warnLegacyAxis = ({ dataSourceName, attributeName }) => {
|
|
4685
4964
|
if (!isDevEnvironment)
|
|
@@ -4719,7 +4998,7 @@ const resolveAxis = (source) => {
|
|
|
4719
4998
|
* Источник питает ось значений.
|
|
4720
4999
|
*
|
|
4721
5000
|
* Отсутствие `axis.type` означает `"y"`: конфиг без явного типа — самый частый случай,
|
|
4722
|
-
* и
|
|
5001
|
+
* и такой источник не должен выпадать из графика.
|
|
4723
5002
|
*/
|
|
4724
5003
|
const isValueAxis = (source) => (resolveAxis(source).type ?? "y") === "y";
|
|
4725
5004
|
/** Сторона оси; отсутствие `axis.side` означает `"left"`. */
|
|
@@ -4784,161 +5063,40 @@ const fetchQueryDescription = (api, { ds, query, parameters }) => {
|
|
|
4784
5063
|
return request;
|
|
4785
5064
|
};
|
|
4786
5065
|
|
|
4787
|
-
const
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
const filterName = `${FILTER_PREFIX}${name}`;
|
|
4800
|
-
// Пока фильтр не применён, границы `%name.min` / `%name.max` берём из range-дефолта конфига
|
|
4801
|
-
// (`defaultValue: [from, to]`) — иначе в условие ушёл бы весь массив вместо своей границы.
|
|
4802
|
-
// Проверка плейсхолдеров обязательна: у array-фильтра массивный дефолт подставляется целиком.
|
|
4803
|
-
const rangeDefault = !hasFilter &&
|
|
4804
|
-
Array.isArray(defaultValue) &&
|
|
4805
|
-
(condition.includes(`${filterName}.min`) || condition.includes(`${filterName}.max`))
|
|
4806
|
-
? defaultValue
|
|
4807
|
-
: null;
|
|
4808
|
-
const min = formatConditionValue({
|
|
4809
|
-
value: hasFilter ? filters[name].min : rangeDefault?.[0] ?? null,
|
|
4810
|
-
checkQuotes: !isSingle,
|
|
4811
|
-
isSetParams,
|
|
4812
|
-
});
|
|
4813
|
-
const max = formatConditionValue({
|
|
4814
|
-
value: hasFilter ? filters[name].max : rangeDefault?.[1] ?? null,
|
|
4815
|
-
checkQuotes: !isSingle,
|
|
4816
|
-
isSetParams,
|
|
4817
|
-
});
|
|
4818
|
-
const value = formatConditionValue({
|
|
4819
|
-
value: hasFilter ? filters[name].value : filters[name] === undefined ? defaultValue : "",
|
|
4820
|
-
defaultValue,
|
|
4821
|
-
checkQuotes: !isSingle,
|
|
4822
|
-
isSetParams,
|
|
4823
|
-
});
|
|
4824
|
-
const hasMin = min !== "" && min !== null && min !== undefined;
|
|
4825
|
-
const hasMax = max !== "" && max !== null && max !== undefined;
|
|
4826
|
-
let result = condition;
|
|
4827
|
-
if (hasMin || hasMax) {
|
|
4828
|
-
if (hasMin) {
|
|
4829
|
-
result = result.replace(new RegExp(`${filterName}\\.min`), min);
|
|
4830
|
-
}
|
|
4831
|
-
if (hasMax) {
|
|
4832
|
-
result = result.replace(new RegExp(`${filterName}\\.max`), max);
|
|
4833
|
-
}
|
|
4834
|
-
}
|
|
4835
|
-
else if (value) {
|
|
4836
|
-
result = result.replace(new RegExp(filterName), value);
|
|
4837
|
-
}
|
|
4838
|
-
return isSingle && isNumeric(result) ? Number(result) : result;
|
|
4839
|
-
};
|
|
4840
|
-
const applyVarsToCondition = ({ section, configFilters, filters, attributes, layerParams, eqlParameters, geometry, extent, zoomLevel, projectName, projectAlias, isSetParams, }) => {
|
|
5066
|
+
const toSources = ({ configFilters, filters, dataSources, dashboard, attributes, geometry, extent, zoomLevel, projectName, projectAlias, }) => ({
|
|
5067
|
+
widget: { selectedFilters: filters, configFilters, dataSources },
|
|
5068
|
+
dashboard,
|
|
5069
|
+
attributes,
|
|
5070
|
+
geometry,
|
|
5071
|
+
extent,
|
|
5072
|
+
zoomLevel,
|
|
5073
|
+
projectName,
|
|
5074
|
+
projectAlias,
|
|
5075
|
+
});
|
|
5076
|
+
/** Подстановки в секции условия (строке или массиве секций) — без разбора set-параметров. */
|
|
5077
|
+
const applyVarsToCondition = ({ section, isSetParams, ...params }) => {
|
|
4841
5078
|
if (!section?.length)
|
|
4842
5079
|
return [];
|
|
4843
|
-
const
|
|
4844
|
-
const
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
result[index] = result[index].replace(new RegExp("%geometry"), `'${geometry}'`);
|
|
4848
|
-
}
|
|
4849
|
-
// Системные фильтры вида карты подставляются ДО цикла по `configFilters`: одноимённый
|
|
4850
|
-
// пользовательский фильтр не должен перехватывать `%extent` / `%zoom`. Lookahead `(?![\w.])`
|
|
4851
|
-
// оставляет фильтрам составные формы (`%zoomLevel`, `%extent_id`, `%zoom.min`) — та же
|
|
4852
|
-
// граница, что уже разводит `%name` и `%name2.l1` в `getUpdatingDataSources`.
|
|
4853
|
-
if (extent) {
|
|
4854
|
-
result[index] = result[index].replace(new RegExp(`${FILTER_PREFIX}${EXTENT_FILTER_NAME}(?![\\w.])`, "g"), `'${extent}'`);
|
|
4855
|
-
}
|
|
4856
|
-
// Зум — число, кавычки не ставим.
|
|
4857
|
-
if (!lodash.isNil(zoomLevel)) {
|
|
4858
|
-
result[index] = result[index].replace(new RegExp(`${FILTER_PREFIX}${ZOOM_FILTER_NAME}(?![\\w.])`, "g"), String(zoomLevel));
|
|
4859
|
-
}
|
|
4860
|
-
// Текущий проект — строковая подстановка, поэтому в кавычках, как `%geometry`. Свойства
|
|
4861
|
-
// заменяются раньше голого плейсхолдера, а граница `(?![\w.])` оставляет фильтрам составные
|
|
4862
|
-
// формы (`%project_id`) и не трогает чужое свойство (`%project.foo`).
|
|
4863
|
-
PROJECT_PROPS.forEach(prop => {
|
|
4864
|
-
const projectValue = getProjectValue({ prop, projectName, projectAlias });
|
|
4865
|
-
if (lodash.isNil(projectValue))
|
|
4866
|
-
return;
|
|
4867
|
-
result[index] = result[index].replace(new RegExp(`${FILTER_PREFIX}${PROJECT_FILTER_NAME}${prop ? `\\.${prop}` : ""}(?![\\w.])`, "g"), `'${projectValue}'`);
|
|
4868
|
-
});
|
|
4869
|
-
if (configFilters?.length) {
|
|
4870
|
-
configFilters.forEach(filter => {
|
|
4871
|
-
result[index] = applyFiltersToCondition({
|
|
4872
|
-
filters,
|
|
4873
|
-
condition: result[index],
|
|
4874
|
-
name: filter.name,
|
|
4875
|
-
defaultValue: filter.defaultValue,
|
|
4876
|
-
configFilter: filter,
|
|
4877
|
-
isSetParams,
|
|
4878
|
-
isSingle: isSingle && !Array.isArray(filter.defaultValue),
|
|
4879
|
-
}).toString();
|
|
4880
|
-
});
|
|
4881
|
-
}
|
|
4882
|
-
if (attributes?.length) {
|
|
4883
|
-
attributes.forEach(({ attributeName, value, type: attributeType }) => {
|
|
4884
|
-
result[index] = result[index].replace(new RegExp(`{${attributeName}}`), formatConditionValue({ value: value?.toString(), attributeType, checkQuotes: !isSingle }));
|
|
4885
|
-
});
|
|
4886
|
-
}
|
|
4887
|
-
if (eqlParameters && layerParams) {
|
|
4888
|
-
Object.keys(eqlParameters).forEach(param => {
|
|
4889
|
-
result[index] = result[index].replace(new RegExp(`\\$\\(${param}=`), "***");
|
|
4890
|
-
result[index] = result[index].replace(new RegExp(param), formatConditionValue({ value: layerParams[param], checkQuotes: !isSingle }));
|
|
4891
|
-
result[index] = result[index].replace(new RegExp("\\*\\*\\*"), `$(${param}=`);
|
|
4892
|
-
});
|
|
4893
|
-
}
|
|
4894
|
-
});
|
|
4895
|
-
return isSingle ? result?.[0] : result;
|
|
4896
|
-
};
|
|
4897
|
-
const formatSingleCondition = (condition, { configFilters, filters, attributes, eqlParameters, layerParams, geometry, extent, zoomLevel, projectName, projectAlias, }) => {
|
|
4898
|
-
const setParams = condition.match(new RegExp("\\$\\([^)]+\\)", "g"));
|
|
4899
|
-
const setParamsSection = applyVarsToCondition({
|
|
4900
|
-
section: setParams,
|
|
4901
|
-
configFilters,
|
|
4902
|
-
filters,
|
|
4903
|
-
attributes,
|
|
4904
|
-
eqlParameters,
|
|
4905
|
-
layerParams,
|
|
4906
|
-
geometry,
|
|
4907
|
-
extent,
|
|
4908
|
-
zoomLevel,
|
|
4909
|
-
projectName,
|
|
4910
|
-
projectAlias,
|
|
4911
|
-
isSetParams: true,
|
|
4912
|
-
});
|
|
4913
|
-
const splitter = " AND ";
|
|
4914
|
-
const setParamsLength = setParams?.join("").length || 0;
|
|
4915
|
-
const conditionSection = applyVarsToCondition({
|
|
4916
|
-
section: condition.substring(setParamsLength).split(splitter),
|
|
4917
|
-
configFilters,
|
|
4918
|
-
filters,
|
|
4919
|
-
attributes,
|
|
4920
|
-
eqlParameters,
|
|
4921
|
-
layerParams,
|
|
4922
|
-
geometry,
|
|
4923
|
-
extent,
|
|
4924
|
-
zoomLevel,
|
|
4925
|
-
projectName,
|
|
4926
|
-
projectAlias,
|
|
4927
|
-
});
|
|
4928
|
-
return setParamsSection?.length && conditionSection.length
|
|
4929
|
-
? [setParamsSection.join(""), conditionSection.join(splitter)].join(" ")
|
|
4930
|
-
: setParamsSection?.length
|
|
4931
|
-
? setParamsSection.join("")
|
|
4932
|
-
: conditionSection.join(splitter);
|
|
5080
|
+
const sources = toSources(params);
|
|
5081
|
+
const { eqlParameters, layerParams } = params;
|
|
5082
|
+
const format = (item) => formatConditionSection(item, sources, { eqlParameters, layerParams, isSetParams });
|
|
5083
|
+
return (typeof section === "string" ? format(section) : section.map(format));
|
|
4933
5084
|
};
|
|
5085
|
+
/**
|
|
5086
|
+
* Условие слоя с подстановками строк конфига: фильтры (`%filter`, `$dashboard:filter`), атрибуты
|
|
5087
|
+
* объекта, системные значения карты и проекта, eql-параметры слоя. Пустое значение фильтра
|
|
5088
|
+
* оставляет плейсхолдер нетронутым.
|
|
5089
|
+
*/
|
|
4934
5090
|
const formatDataSourceCondition = ({ condition, ...rest }) => {
|
|
5091
|
+
const sources = toSources(rest);
|
|
5092
|
+
const eql = { eqlParameters: rest.eqlParameters, layerParams: rest.layerParams };
|
|
4935
5093
|
if (Array.isArray(condition)) {
|
|
4936
|
-
return condition.map(item =>
|
|
5094
|
+
return condition.map(item => formatConditionString(item, sources, eql));
|
|
4937
5095
|
}
|
|
4938
5096
|
if (!condition) {
|
|
4939
5097
|
return "";
|
|
4940
5098
|
}
|
|
4941
|
-
return
|
|
5099
|
+
return formatConditionString(condition, sources, eql);
|
|
4942
5100
|
};
|
|
4943
5101
|
|
|
4944
5102
|
const DashboardChipsContainer = styled(uilibGl.Flex).withConfig({ displayName: "DashboardChipsContainer", componentId: "sc-pzlzu7" }) `
|
|
@@ -5109,10 +5267,9 @@ const noMarginMixin = styled.css `
|
|
|
5109
5267
|
/**
|
|
5110
5268
|
* Обёртка значения элемента.
|
|
5111
5269
|
*
|
|
5112
|
-
* Контейнеры, владеющие своим корнем (`ROOT_OWNING_TEMPLATES`), сюда
|
|
5113
|
-
*
|
|
5114
|
-
* `$sizeTransparent` поэтому работает только на
|
|
5115
|
-
* на `useContainerRoot`.
|
|
5270
|
+
* Контейнеры, владеющие своим корнем (`ROOT_OWNING_TEMPLATES`), сюда не попадают — обёртка
|
|
5271
|
+
* продублировала бы их `data-id`, `data-templatename` и `style` вторым узлом DOM.
|
|
5272
|
+
* `$sizeTransparent` поэтому работает только на контейнерах без `useContainerRoot`.
|
|
5116
5273
|
*/
|
|
5117
5274
|
const ElementValueWrapper = styled.div.withConfig({ displayName: "ElementValueWrapper", componentId: "sc-x3ra9l" }) `
|
|
5118
5275
|
transition: background-color ${uilibGl.transition.toggle};
|
|
@@ -5141,8 +5298,8 @@ const ElementValueWrapper = styled.div.withConfig({ displayName: "ElementValueWr
|
|
|
5141
5298
|
* а доля из `options.width` достаётся только телу. Поэтому `id`, `data-templatename`, авторский
|
|
5142
5299
|
* `style` и `$sizeCss` живут здесь, а не на теле — см. `useContainerRoot`.
|
|
5143
5300
|
*
|
|
5144
|
-
* Всегда flex-колонка:
|
|
5145
|
-
*
|
|
5301
|
+
* Всегда flex-колонка: в блочной обёртке вертикальные отступы между заголовком и телом
|
|
5302
|
+
* схлопывались бы.
|
|
5146
5303
|
*/
|
|
5147
5304
|
const ContainerRoot = styled(uilibGl.Flex).withConfig({ displayName: "ContainerRoot", componentId: "sc-1m2pft5" }) `
|
|
5148
5305
|
flex-direction: column;
|
|
@@ -5610,6 +5767,34 @@ const getChartFilterName = (relatedDataSources) => {
|
|
|
5610
5767
|
return axes?.[0]?.filterName;
|
|
5611
5768
|
};
|
|
5612
5769
|
|
|
5770
|
+
const isValueChanged = (value, prevValue, configFilter) => {
|
|
5771
|
+
const isObjectValue = isTreeFilterValue(value, configFilter) ||
|
|
5772
|
+
isTreeFilterValue(prevValue, configFilter) ||
|
|
5773
|
+
isFeaturesFilterValue(value) ||
|
|
5774
|
+
isFeaturesFilterValue(prevValue);
|
|
5775
|
+
if (isObjectValue) {
|
|
5776
|
+
return JSON.stringify(value) !== JSON.stringify(prevValue);
|
|
5777
|
+
}
|
|
5778
|
+
if (Array.isArray(value)) {
|
|
5779
|
+
return (value.length !== prevValue?.length ||
|
|
5780
|
+
value.some((item, index) => item !== prevValue?.[index]));
|
|
5781
|
+
}
|
|
5782
|
+
return value !== prevValue;
|
|
5783
|
+
};
|
|
5784
|
+
/** Имена фильтров, чьё выбранное значение изменилось с прошлого снимка. */
|
|
5785
|
+
const getChangedFilterNames = (filters, prevFilters, configFilters) => filters
|
|
5786
|
+
? Object.keys(filters).filter(key => isValueChanged(filters[key].value, prevFilters[key]?.value, getConfigFilter(key, configFilters)))
|
|
5787
|
+
: [];
|
|
5788
|
+
/**
|
|
5789
|
+
* Имена фильтров, чей дефолт изменился (`defaultValue: "$dashboard:year"` при смене фильтра дашборда),
|
|
5790
|
+
* пока пользователь не выбрал своё значение, — для потребителей это то же, что смена значения.
|
|
5791
|
+
*/
|
|
5792
|
+
const getChangedDefaultNames = (configFilters, prevConfigFilters, filters) => (configFilters ?? [])
|
|
5793
|
+
.filter(({ name, defaultValue }) => filters?.[name] === undefined &&
|
|
5794
|
+
prevConfigFilters !== undefined &&
|
|
5795
|
+
!lodash.isEqual(defaultValue, getConfigFilter(name, prevConfigFilters)?.defaultValue))
|
|
5796
|
+
.map(({ name }) => name);
|
|
5797
|
+
|
|
5613
5798
|
function getValueIndex(items, attributes) {
|
|
5614
5799
|
return items?.findIndex(({ name }) => name.toString() === attributes.value?.toString());
|
|
5615
5800
|
}
|
|
@@ -6254,29 +6439,227 @@ const useAutoCompleteControl = (items) => {
|
|
|
6254
6439
|
.map(item => ({ text: String(item), value: String(item) })), [items]);
|
|
6255
6440
|
const onChange = React.useCallback((newValue) => setValue(newValue), []);
|
|
6256
6441
|
return {
|
|
6257
|
-
value,
|
|
6258
|
-
setValue,
|
|
6259
|
-
onChange,
|
|
6260
|
-
options: staticOptions.length ? staticOptions : options,
|
|
6261
|
-
setOptions,
|
|
6442
|
+
value,
|
|
6443
|
+
setValue,
|
|
6444
|
+
onChange,
|
|
6445
|
+
options: staticOptions.length ? staticOptions : options,
|
|
6446
|
+
setOptions,
|
|
6447
|
+
};
|
|
6448
|
+
};
|
|
6449
|
+
|
|
6450
|
+
/**
|
|
6451
|
+
* Пропсы хоста фонового слоя для корней, которые не берут пропсы из `useWrapperSize`
|
|
6452
|
+
* (ставят `id`/`style` руками — `Title`, `Icon`, `Tabs`, `AddFeature`, `ExportPdf`, …).
|
|
6453
|
+
*
|
|
6454
|
+
* Контейнеры на `useContainerRoot` этот хук напрямую не зовут: `useWrapperSize` вызывает его
|
|
6455
|
+
* сам и кладёт результат в `root` — так признаки хоста считаются в одном месте.
|
|
6456
|
+
*/
|
|
6457
|
+
const useBgImageHost = (elementConfig) => {
|
|
6458
|
+
const hasBgImage = React.useMemo(() => hasContainerBgImage(elementConfig), [elementConfig]);
|
|
6459
|
+
const innerPadding = !!elementConfig?.options?.innerPadding;
|
|
6460
|
+
return React.useMemo(() => ({ $hasBgImage: hasBgImage, $innerPadding: innerPadding }), [hasBgImage, innerPadding]);
|
|
6461
|
+
};
|
|
6462
|
+
|
|
6463
|
+
const getPagesFromConfig = (config) => config?.children?.[0]?.children;
|
|
6464
|
+
|
|
6465
|
+
/** Фильтры страницы `pageIndex` (с 1) вместе с общими фильтрами конфига; общие перекрывают одноимённые. */
|
|
6466
|
+
const getPageFilters = (config, pageIndex) => {
|
|
6467
|
+
const page = getPagesFromConfig(config)?.[pageIndex - 1];
|
|
6468
|
+
return [
|
|
6469
|
+
...(page?.filters?.filter(({ name }) => !config?.filters?.some(item => item.name === name)) || []),
|
|
6470
|
+
...(config?.filters || []),
|
|
6471
|
+
];
|
|
6472
|
+
};
|
|
6473
|
+
|
|
6474
|
+
/**
|
|
6475
|
+
* Generic-параметр `T` сейчас идёт как намерение (под какой виджет). Дальнейшее сужение
|
|
6476
|
+
* `config` под конкретный виджет — отдельная задача.
|
|
6477
|
+
*/
|
|
6478
|
+
const useWidgetConfig = (type = exports.WidgetType.Dashboard) => {
|
|
6479
|
+
const { config: configProp, containerIds, projectInfo, layerInfo, isEditing } = useWidgetContext(type);
|
|
6480
|
+
const config = React.useMemo(() => {
|
|
6481
|
+
if (configProp) {
|
|
6482
|
+
return configProp;
|
|
6483
|
+
}
|
|
6484
|
+
if (isEditing) {
|
|
6485
|
+
return type === exports.WidgetType.Dashboard
|
|
6486
|
+
? (projectInfo?.content?.editConfiguration || {})
|
|
6487
|
+
: (layerInfo?.configuration?.editConfiguration || {});
|
|
6488
|
+
}
|
|
6489
|
+
const currentConfig = type === exports.WidgetType.Dashboard
|
|
6490
|
+
? (projectInfo?.content?.dashboardConfiguration || {})
|
|
6491
|
+
: (layerInfo?.configuration?.cardConfiguration || {});
|
|
6492
|
+
if (containerIds?.length) {
|
|
6493
|
+
const elements = containerIds.map(id => findAnd.returnFound(currentConfig, { id })).filter(Boolean);
|
|
6494
|
+
if (!elements.length)
|
|
6495
|
+
return currentConfig;
|
|
6496
|
+
const newConfig = JSON.parse(JSON.stringify(currentConfig));
|
|
6497
|
+
const pages = getPagesFromConfig(newConfig);
|
|
6498
|
+
if (!pages?.[0]?.children)
|
|
6499
|
+
return currentConfig;
|
|
6500
|
+
pages[0].children = elements;
|
|
6501
|
+
return newConfig;
|
|
6502
|
+
}
|
|
6503
|
+
return currentConfig;
|
|
6504
|
+
}, [configProp, containerIds, layerInfo?.configuration?.cardConfiguration, projectInfo?.content?.dashboardConfiguration, type, isEditing]);
|
|
6505
|
+
const pages = React.useMemo(() => (getPagesFromConfig(config) || []), [config]);
|
|
6506
|
+
return {
|
|
6507
|
+
config,
|
|
6508
|
+
pages,
|
|
6509
|
+
};
|
|
6510
|
+
};
|
|
6511
|
+
|
|
6512
|
+
/**
|
|
6513
|
+
* Фильтры ближайшего дашборда для `$dashboard:` в строках конфига виджета `type`. На самом
|
|
6514
|
+
* дашборде стор не нужен (`undefined`): `$dashboard:name` там равен `%name`.
|
|
6515
|
+
*
|
|
6516
|
+
* Страница дашборда собирается напрямую из конфига, а не через `useWidgetPage`: тот сам зовёт этот хук.
|
|
6517
|
+
*/
|
|
6518
|
+
const useDashboardFilterStore = (type) => {
|
|
6519
|
+
const { filters, dataSources, pageIndex } = useWidgetContext(exports.WidgetType.Dashboard);
|
|
6520
|
+
const { config } = useWidgetConfig(exports.WidgetType.Dashboard);
|
|
6521
|
+
const isDashboard = type === exports.WidgetType.Dashboard;
|
|
6522
|
+
const configFilters = React.useMemo(() => (isDashboard ? undefined : getPageFilters(config, pageIndex)), [config, isDashboard, pageIndex]);
|
|
6523
|
+
return React.useMemo(() => (isDashboard ? undefined : { selectedFilters: filters, configFilters, dataSources }), [configFilters, dataSources, filters, isDashboard]);
|
|
6524
|
+
};
|
|
6525
|
+
|
|
6526
|
+
/**
|
|
6527
|
+
* Generic-параметр `T` — намерение под виджет. Дальнейшая параметризация — отдельная задача.
|
|
6528
|
+
*
|
|
6529
|
+
* Дефолты фильтров не-дашборда (карточки) с `$dashboard:` приходят уже разрезолвленными из фильтров
|
|
6530
|
+
* дашборда. Страница дашборда отдаётся как есть: её `currentPage` редактор пишет обратно в конфиг.
|
|
6531
|
+
*/
|
|
6532
|
+
const useWidgetPage = (type = exports.WidgetType.Dashboard) => {
|
|
6533
|
+
const { t } = useGlobalContext();
|
|
6534
|
+
const { pageIndex, changePage, projectInfo, updateProject } = useWidgetContext(type);
|
|
6535
|
+
const { config } = useWidgetConfig(type);
|
|
6536
|
+
const dashboardFilterStore = useDashboardFilterStore(type);
|
|
6537
|
+
const pageFilters = React.useMemo(() => getPageFilters(config, pageIndex), [config, pageIndex]);
|
|
6538
|
+
// Стор дашборда меняется на каждый его фильтр: без ссылок на `$dashboard:` страница от него не зависит.
|
|
6539
|
+
const defaultsStore = React.useMemo(() => (pageFilters.some(({ defaultValue }) => isDashboardReference(defaultValue)) ? dashboardFilterStore : undefined), [dashboardFilterStore, pageFilters]);
|
|
6540
|
+
const resolvedFilters = React.useMemo(() => resolveFilterDefaults(pageFilters, defaultsStore), [defaultsStore, pageFilters]);
|
|
6541
|
+
const filtersRef = React.useRef(resolvedFilters);
|
|
6542
|
+
// Стор дашборда пересобирается и на загрузку его датасорсов — страницу меняют только новые дефолты.
|
|
6543
|
+
if (!lodash.isEqual(filtersRef.current, resolvedFilters)) {
|
|
6544
|
+
filtersRef.current = resolvedFilters;
|
|
6545
|
+
}
|
|
6546
|
+
const filters = filtersRef.current;
|
|
6547
|
+
const currentPage = React.useMemo(() => {
|
|
6548
|
+
const page = getPagesFromConfig(config)?.[pageIndex - 1];
|
|
6549
|
+
return {
|
|
6550
|
+
...(page || {}),
|
|
6551
|
+
header: page?.header || config.header,
|
|
6552
|
+
dataSources: [
|
|
6553
|
+
...(page?.dataSources?.filter(({ name }) => !config.dataSources?.some(item => item.name === name)) || []),
|
|
6554
|
+
...(config.dataSources || []),
|
|
6555
|
+
],
|
|
6556
|
+
filters,
|
|
6557
|
+
layers: page?.layers?.map((layer) => ({
|
|
6558
|
+
...layer,
|
|
6559
|
+
isVisible: layer.isVisible ?? true,
|
|
6560
|
+
})) || [],
|
|
6561
|
+
};
|
|
6562
|
+
}, [config, pageIndex, filters]);
|
|
6563
|
+
const updateConfigPage = React.useCallback((newConfig) => {
|
|
6564
|
+
const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
|
|
6565
|
+
const pages = getPagesFromProjectInfo(newProjectInfo);
|
|
6566
|
+
if (!pages?.[pageIndex - 1])
|
|
6567
|
+
return;
|
|
6568
|
+
pages[pageIndex - 1] = { ...pages[pageIndex - 1], ...newConfig };
|
|
6569
|
+
updateProject(newProjectInfo);
|
|
6570
|
+
}, [projectInfo, pageIndex, updateProject]);
|
|
6571
|
+
const addConfigPage = React.useCallback(() => {
|
|
6572
|
+
const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
|
|
6573
|
+
const pages = getPagesFromProjectInfo(newProjectInfo);
|
|
6574
|
+
if (!pages)
|
|
6575
|
+
return;
|
|
6576
|
+
pages.push(createConfigPage({ pages, defaultTitle: t("page", { ns: "dashboard", defaultValue: "Страница" }) }));
|
|
6577
|
+
updateProject(newProjectInfo);
|
|
6578
|
+
changePage(pages.length);
|
|
6579
|
+
}, [changePage, projectInfo, t, updateProject]);
|
|
6580
|
+
const deleteConfigPage = React.useCallback((index) => {
|
|
6581
|
+
const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
|
|
6582
|
+
const pages = getPagesFromProjectInfo(newProjectInfo);
|
|
6583
|
+
if (!pages?.[index] || pages.length === 1)
|
|
6584
|
+
return;
|
|
6585
|
+
pages.splice(index, 1);
|
|
6586
|
+
updateProject(newProjectInfo);
|
|
6587
|
+
if (!pages[pageIndex]) {
|
|
6588
|
+
changePage(pageIndex > 1 ? pageIndex - 1 : 1);
|
|
6589
|
+
}
|
|
6590
|
+
}, [changePage, pageIndex, projectInfo, updateProject]);
|
|
6591
|
+
const updateConfigLayer = React.useCallback((name, newProps) => {
|
|
6592
|
+
const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
|
|
6593
|
+
const pages = getPagesFromProjectInfo(newProjectInfo);
|
|
6594
|
+
const page = pages?.[pageIndex - 1];
|
|
6595
|
+
const index = page?.layers?.findIndex(layer => layer.name === name) ?? -1;
|
|
6596
|
+
if (index === -1)
|
|
6597
|
+
return;
|
|
6598
|
+
page.layers[index] = { ...page.layers[index], ...newProps };
|
|
6599
|
+
updateProject(newProjectInfo);
|
|
6600
|
+
}, [projectInfo, pageIndex, updateProject]);
|
|
6601
|
+
const updateConfigLayers = React.useCallback((props) => {
|
|
6602
|
+
const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
|
|
6603
|
+
const pages = getPagesFromProjectInfo(newProjectInfo);
|
|
6604
|
+
const page = pages?.[pageIndex - 1];
|
|
6605
|
+
if (!page?.layers?.length)
|
|
6606
|
+
return;
|
|
6607
|
+
page.layers = page.layers.map(layer => ({
|
|
6608
|
+
...layer,
|
|
6609
|
+
...(props[layer.name] || {}),
|
|
6610
|
+
}));
|
|
6611
|
+
updateProject(newProjectInfo);
|
|
6612
|
+
}, [projectInfo, pageIndex, updateProject]);
|
|
6613
|
+
return {
|
|
6614
|
+
pageIndex,
|
|
6615
|
+
currentPage,
|
|
6616
|
+
changePage,
|
|
6617
|
+
updateConfigPage,
|
|
6618
|
+
addConfigPage,
|
|
6619
|
+
deleteConfigPage,
|
|
6620
|
+
updateConfigLayer,
|
|
6621
|
+
updateConfigLayers,
|
|
6262
6622
|
};
|
|
6263
6623
|
};
|
|
6264
6624
|
|
|
6265
6625
|
/**
|
|
6266
|
-
*
|
|
6267
|
-
*
|
|
6268
|
-
*
|
|
6269
|
-
* Контейнеры на `useContainerRoot` этот хук напрямую не зовут: `useWrapperSize` вызывает его
|
|
6270
|
-
* сам и кладёт результат в `root` — так признаки хоста считаются в одном месте.
|
|
6626
|
+
* Все источники подстановок строк конфига для виджета `type`: его фильтры (`%`), фильтры и
|
|
6627
|
+
* датасорсы дашборда (`$dashboard:`), атрибуты объекта, вид карты и проект.
|
|
6628
|
+
* Атрибуты — объекта виджета; строки `DataSource` подменяют их своими при рендере.
|
|
6271
6629
|
*/
|
|
6272
|
-
const
|
|
6273
|
-
const
|
|
6274
|
-
const
|
|
6275
|
-
|
|
6630
|
+
const useConfigStringSources = (type) => {
|
|
6631
|
+
const { ewktGeometry, ewktExtent, zoomLevel, projectName, projectAlias } = useGlobalContext();
|
|
6632
|
+
const { filters, dataSources, attributes, layerInfo } = useWidgetContext(type);
|
|
6633
|
+
const { currentPage } = useWidgetPage(type);
|
|
6634
|
+
const dashboard = useDashboardFilterStore(type);
|
|
6635
|
+
return React.useMemo(() => ({
|
|
6636
|
+
widget: { selectedFilters: filters, configFilters: currentPage?.filters, dataSources },
|
|
6637
|
+
dashboard,
|
|
6638
|
+
attributes,
|
|
6639
|
+
layerInfo,
|
|
6640
|
+
geometry: ewktGeometry,
|
|
6641
|
+
extent: ewktExtent,
|
|
6642
|
+
zoomLevel,
|
|
6643
|
+
projectName,
|
|
6644
|
+
projectAlias,
|
|
6645
|
+
}), [
|
|
6646
|
+
attributes,
|
|
6647
|
+
currentPage?.filters,
|
|
6648
|
+
dashboard,
|
|
6649
|
+
dataSources,
|
|
6650
|
+
ewktExtent,
|
|
6651
|
+
ewktGeometry,
|
|
6652
|
+
filters,
|
|
6653
|
+
layerInfo,
|
|
6654
|
+
projectAlias,
|
|
6655
|
+
projectName,
|
|
6656
|
+
zoomLevel,
|
|
6657
|
+
]);
|
|
6276
6658
|
};
|
|
6277
6659
|
|
|
6278
6660
|
const useRenderContainerItem = (type, renderElement) => {
|
|
6279
6661
|
const { config, layerInfo, selectedTabId, attributes } = useWidgetContext(type);
|
|
6662
|
+
const configStringSources = useConfigStringSources(type);
|
|
6280
6663
|
return React.useCallback((elementConfig, attribute) => {
|
|
6281
6664
|
const { id, options, style, children } = elementConfig || {};
|
|
6282
6665
|
const { hideEmpty, innerTemplateStyle } = options || {};
|
|
@@ -6309,6 +6692,7 @@ const useRenderContainerItem = (type, renderElement) => {
|
|
|
6309
6692
|
attributes,
|
|
6310
6693
|
layerInfo,
|
|
6311
6694
|
type,
|
|
6695
|
+
configStringSources,
|
|
6312
6696
|
})
|
|
6313
6697
|
: renderElement;
|
|
6314
6698
|
const value = render({ id: "value" });
|
|
@@ -6321,7 +6705,7 @@ const useRenderContainerItem = (type, renderElement) => {
|
|
|
6321
6705
|
hasUnits,
|
|
6322
6706
|
render,
|
|
6323
6707
|
};
|
|
6324
|
-
}, [config, layerInfo, selectedTabId, attributes, type, renderElement]);
|
|
6708
|
+
}, [config, layerInfo, selectedTabId, attributes, type, renderElement, configStringSources]);
|
|
6325
6709
|
};
|
|
6326
6710
|
|
|
6327
6711
|
const customModes = MapboxDraw.modes;
|
|
@@ -7071,8 +7455,8 @@ const useContainerAttributes = ({ elementConfig, type, renderElement }) => {
|
|
|
7071
7455
|
* Размеры уходят styled-пропом (класс), а не inline-стилем, поэтому перебиваются снаружи
|
|
7072
7456
|
* без `!important`. Авторский `style` остаётся inline — у него приоритет по замыслу автора конфига.
|
|
7073
7457
|
*
|
|
7074
|
-
* `data-id` и `$noMargin`
|
|
7075
|
-
* на второй узел поверх
|
|
7458
|
+
* `data-id` и `$noMargin` живут на самом корне: внешняя обёртка `ElementValueWrapper` повесила бы
|
|
7459
|
+
* их на второй узел поверх него, дублируя стили, поэтому для таких контейнеров она не создаётся
|
|
7076
7460
|
* (см. `ROOT_OWNING_TEMPLATES`).
|
|
7077
7461
|
*
|
|
7078
7462
|
* Пропсы хоста фонового слоя (`$hasBgImage`, `$innerPadding`) подмешиваются здесь, а не в каждом
|
|
@@ -7485,132 +7869,6 @@ const getModalsDataSources = (config, modalIds) => lodash.uniqBy((config?.modals
|
|
|
7485
7869
|
.filter(({ id }) => !modalIds || modalIds.includes(id))
|
|
7486
7870
|
.flatMap(({ dataSources }) => dataSources ?? []), "name");
|
|
7487
7871
|
|
|
7488
|
-
/**
|
|
7489
|
-
* Generic-параметр `T` сейчас идёт как намерение (под какой виджет). Дальнейшее сужение
|
|
7490
|
-
* `config` под конкретный виджет — отдельная задача.
|
|
7491
|
-
*/
|
|
7492
|
-
const useWidgetConfig = (type = exports.WidgetType.Dashboard) => {
|
|
7493
|
-
const { config: configProp, containerIds, projectInfo, layerInfo, isEditing } = useWidgetContext(type);
|
|
7494
|
-
const config = React.useMemo(() => {
|
|
7495
|
-
if (configProp) {
|
|
7496
|
-
return configProp;
|
|
7497
|
-
}
|
|
7498
|
-
if (isEditing) {
|
|
7499
|
-
return type === exports.WidgetType.Dashboard
|
|
7500
|
-
? (projectInfo?.content?.editConfiguration || {})
|
|
7501
|
-
: (layerInfo?.configuration?.editConfiguration || {});
|
|
7502
|
-
}
|
|
7503
|
-
const currentConfig = type === exports.WidgetType.Dashboard
|
|
7504
|
-
? (projectInfo?.content?.dashboardConfiguration || {})
|
|
7505
|
-
: (layerInfo?.configuration?.cardConfiguration || {});
|
|
7506
|
-
if (containerIds?.length) {
|
|
7507
|
-
const elements = containerIds.map(id => findAnd.returnFound(currentConfig, { id })).filter(Boolean);
|
|
7508
|
-
if (!elements.length)
|
|
7509
|
-
return currentConfig;
|
|
7510
|
-
const newConfig = JSON.parse(JSON.stringify(currentConfig));
|
|
7511
|
-
const pages = getPagesFromConfig(newConfig);
|
|
7512
|
-
if (!pages?.[0]?.children)
|
|
7513
|
-
return currentConfig;
|
|
7514
|
-
pages[0].children = elements;
|
|
7515
|
-
return newConfig;
|
|
7516
|
-
}
|
|
7517
|
-
return currentConfig;
|
|
7518
|
-
}, [configProp, containerIds, layerInfo?.configuration?.cardConfiguration, projectInfo?.content?.dashboardConfiguration, type, isEditing]);
|
|
7519
|
-
const pages = React.useMemo(() => (getPagesFromConfig(config) || []), [config]);
|
|
7520
|
-
return {
|
|
7521
|
-
config,
|
|
7522
|
-
pages,
|
|
7523
|
-
};
|
|
7524
|
-
};
|
|
7525
|
-
|
|
7526
|
-
/**
|
|
7527
|
-
* Generic-параметр `T` — намерение под виджет. Дальнейшая параметризация — отдельная задача.
|
|
7528
|
-
*/
|
|
7529
|
-
const useWidgetPage = (type = exports.WidgetType.Dashboard) => {
|
|
7530
|
-
const { t } = useGlobalContext();
|
|
7531
|
-
const { pageIndex, changePage, projectInfo, updateProject } = useWidgetContext(type);
|
|
7532
|
-
const { config } = useWidgetConfig(type);
|
|
7533
|
-
const currentPage = React.useMemo(() => {
|
|
7534
|
-
const page = getPagesFromConfig(config)?.[pageIndex - 1];
|
|
7535
|
-
return {
|
|
7536
|
-
...(page || {}),
|
|
7537
|
-
header: page?.header || config.header,
|
|
7538
|
-
dataSources: [
|
|
7539
|
-
...(page?.dataSources?.filter(({ name }) => !config.dataSources?.some(item => item.name === name)) || []),
|
|
7540
|
-
...(config.dataSources || [])
|
|
7541
|
-
],
|
|
7542
|
-
filters: [
|
|
7543
|
-
...(page?.filters?.filter(({ name }) => !config.filters?.some(item => item.name === name)) || []),
|
|
7544
|
-
...(config.filters || [])
|
|
7545
|
-
],
|
|
7546
|
-
layers: page?.layers?.map((layer) => ({
|
|
7547
|
-
...layer,
|
|
7548
|
-
isVisible: layer.isVisible ?? true
|
|
7549
|
-
})) || []
|
|
7550
|
-
};
|
|
7551
|
-
}, [config, pageIndex]);
|
|
7552
|
-
const updateConfigPage = React.useCallback((newConfig) => {
|
|
7553
|
-
const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
|
|
7554
|
-
const pages = getPagesFromProjectInfo(newProjectInfo);
|
|
7555
|
-
if (!pages?.[pageIndex - 1])
|
|
7556
|
-
return;
|
|
7557
|
-
pages[pageIndex - 1] = { ...pages[pageIndex - 1], ...newConfig };
|
|
7558
|
-
updateProject(newProjectInfo);
|
|
7559
|
-
}, [projectInfo, pageIndex, updateProject]);
|
|
7560
|
-
const addConfigPage = React.useCallback(() => {
|
|
7561
|
-
const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
|
|
7562
|
-
const pages = getPagesFromProjectInfo(newProjectInfo);
|
|
7563
|
-
if (!pages)
|
|
7564
|
-
return;
|
|
7565
|
-
pages.push(createConfigPage({ pages, defaultTitle: t("page", { ns: "dashboard", defaultValue: "Страница" }) }));
|
|
7566
|
-
updateProject(newProjectInfo);
|
|
7567
|
-
changePage(pages.length);
|
|
7568
|
-
}, [changePage, projectInfo, t, updateProject]);
|
|
7569
|
-
const deleteConfigPage = React.useCallback((index) => {
|
|
7570
|
-
const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
|
|
7571
|
-
const pages = getPagesFromProjectInfo(newProjectInfo);
|
|
7572
|
-
if (!pages?.[index] || pages.length === 1)
|
|
7573
|
-
return;
|
|
7574
|
-
pages.splice(index, 1);
|
|
7575
|
-
updateProject(newProjectInfo);
|
|
7576
|
-
if (!pages[pageIndex]) {
|
|
7577
|
-
changePage(pageIndex > 1 ? pageIndex - 1 : 1);
|
|
7578
|
-
}
|
|
7579
|
-
}, [changePage, pageIndex, projectInfo, updateProject]);
|
|
7580
|
-
const updateConfigLayer = React.useCallback((name, newProps) => {
|
|
7581
|
-
const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
|
|
7582
|
-
const pages = getPagesFromProjectInfo(newProjectInfo);
|
|
7583
|
-
const page = pages?.[pageIndex - 1];
|
|
7584
|
-
const index = page?.layers?.findIndex(layer => layer.name === name) ?? -1;
|
|
7585
|
-
if (index === -1)
|
|
7586
|
-
return;
|
|
7587
|
-
page.layers[index] = { ...page.layers[index], ...newProps };
|
|
7588
|
-
updateProject(newProjectInfo);
|
|
7589
|
-
}, [projectInfo, pageIndex, updateProject]);
|
|
7590
|
-
const updateConfigLayers = React.useCallback((props) => {
|
|
7591
|
-
const newProjectInfo = JSON.parse(JSON.stringify(projectInfo));
|
|
7592
|
-
const pages = getPagesFromProjectInfo(newProjectInfo);
|
|
7593
|
-
const page = pages?.[pageIndex - 1];
|
|
7594
|
-
if (!page?.layers?.length)
|
|
7595
|
-
return;
|
|
7596
|
-
page.layers = page.layers.map(layer => ({
|
|
7597
|
-
...layer,
|
|
7598
|
-
...(props[layer.name] || {}),
|
|
7599
|
-
}));
|
|
7600
|
-
updateProject(newProjectInfo);
|
|
7601
|
-
}, [projectInfo, pageIndex, updateProject]);
|
|
7602
|
-
return {
|
|
7603
|
-
pageIndex,
|
|
7604
|
-
currentPage,
|
|
7605
|
-
changePage,
|
|
7606
|
-
updateConfigPage,
|
|
7607
|
-
addConfigPage,
|
|
7608
|
-
deleteConfigPage,
|
|
7609
|
-
updateConfigLayer,
|
|
7610
|
-
updateConfigLayers,
|
|
7611
|
-
};
|
|
7612
|
-
};
|
|
7613
|
-
|
|
7614
7872
|
/**
|
|
7615
7873
|
* Конфиги всех источников, доступных контейнерам виджета: страница с корнем и все модалки.
|
|
7616
7874
|
* Одноимённый источник модалки перекрывается страничным. Только для поиска по имени — в конфиг
|
|
@@ -7711,6 +7969,7 @@ const useHeaderRender = (elementConfig, type = exports.WidgetType.Dashboard) =>
|
|
|
7711
7969
|
const { layerInfo, attributes, expandedContainers, selectedTabId, setSelectedTabId, } = useWidgetContext(type);
|
|
7712
7970
|
const { config } = useWidgetConfig(type);
|
|
7713
7971
|
const { pageIndex } = useWidgetPage(type);
|
|
7972
|
+
const configStringSources = useConfigStringSources(type);
|
|
7714
7973
|
return React.useMemo(() => getRenderElement({
|
|
7715
7974
|
config,
|
|
7716
7975
|
elementConfig,
|
|
@@ -7720,8 +7979,10 @@ const useHeaderRender = (elementConfig, type = exports.WidgetType.Dashboard) =>
|
|
|
7720
7979
|
selectedTabId,
|
|
7721
7980
|
setSelectedTabId,
|
|
7722
7981
|
pageIndex,
|
|
7982
|
+
configStringSources,
|
|
7723
7983
|
}), [
|
|
7724
7984
|
config,
|
|
7985
|
+
configStringSources,
|
|
7725
7986
|
elementConfig,
|
|
7726
7987
|
attributes,
|
|
7727
7988
|
layerInfo,
|
|
@@ -7779,9 +8040,11 @@ const useDataSourceLoading = (type) => {
|
|
|
7779
8040
|
const useDataSources = ({ type: widgetType, config, attributes, filters, layerParams, eqlParameters, }) => {
|
|
7780
8041
|
const { ewktGeometry, ewktExtent, zoomLevel, projectName, projectAlias, api } = useGlobalContext();
|
|
7781
8042
|
const { dataSources, layerInfo } = useWidgetContext(widgetType);
|
|
7782
|
-
const
|
|
8043
|
+
const dashboard = useDashboardFilterStore(widgetType ?? exports.WidgetType.Dashboard);
|
|
7783
8044
|
const { filters: configFilters, dataSources: configDataSources } = config || {};
|
|
7784
8045
|
const prevFilters = React.useRef({});
|
|
8046
|
+
const prevDashboardFilters = React.useRef(dashboard?.selectedFilters ?? {});
|
|
8047
|
+
const prevConfigFilters = React.useRef(configFilters);
|
|
7785
8048
|
const inFlightRequests = React.useRef(new Map());
|
|
7786
8049
|
const getDataSourcePromises = React.useCallback(async ({ ds, query, parameters, layerName, limit, condition, url, resourceId, fileName, methodName, }, newFilters, offset = 0) => {
|
|
7787
8050
|
// Дедуп in-flight по сигнатуре резолвленного запроса: если идентичный
|
|
@@ -7811,7 +8074,7 @@ const useDataSources = ({ type: widgetType, config, attributes, filters, layerPa
|
|
|
7811
8074
|
projectAlias,
|
|
7812
8075
|
layerInfo,
|
|
7813
8076
|
dataSources,
|
|
7814
|
-
|
|
8077
|
+
dashboard,
|
|
7815
8078
|
});
|
|
7816
8079
|
if (resourceId) {
|
|
7817
8080
|
const newSignature = JSON.stringify(["python", resourceId, newParams, methodName, fileName]);
|
|
@@ -7882,6 +8145,8 @@ const useDataSources = ({ type: widgetType, config, attributes, filters, layerPa
|
|
|
7882
8145
|
condition,
|
|
7883
8146
|
configFilters,
|
|
7884
8147
|
filters: selectedFilters,
|
|
8148
|
+
dataSources,
|
|
8149
|
+
dashboard,
|
|
7885
8150
|
geometry: ewktGeometry,
|
|
7886
8151
|
extent: ewktExtent,
|
|
7887
8152
|
zoomLevel,
|
|
@@ -7916,50 +8181,34 @@ const useDataSources = ({ type: widgetType, config, attributes, filters, layerPa
|
|
|
7916
8181
|
attributes,
|
|
7917
8182
|
layerParams,
|
|
7918
8183
|
layerInfo,
|
|
7919
|
-
projectDataSources,
|
|
7920
8184
|
eqlParameters,
|
|
8185
|
+
dashboard,
|
|
7921
8186
|
]);
|
|
8187
|
+
/**
|
|
8188
|
+
* Датасорсы, которые нужно перезапросить: их параметры ссылаются на изменившийся фильтр виджета
|
|
8189
|
+
* (`%name`, в том числе через дефолт `$dashboard:`) или дашборда (`$dashboard:name`).
|
|
8190
|
+
* Датасорсы слоя без `query` перезапрашиваются на любое изменение.
|
|
8191
|
+
*/
|
|
7922
8192
|
const getUpdatingDataSources = React.useCallback(() => {
|
|
7923
|
-
const
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
if (Array.isArray(value)) {
|
|
7936
|
-
return (value.length !== prevValue?.length ||
|
|
7937
|
-
value.some((item, index) => item !== prevValue?.[index]));
|
|
7938
|
-
}
|
|
7939
|
-
return value !== prevValue;
|
|
7940
|
-
})
|
|
7941
|
-
: [];
|
|
7942
|
-
if (!diffFilterNames.length)
|
|
7943
|
-
return;
|
|
7944
|
-
const filteredDataSources = configDataSources?.filter(({ parameters = {}, query }) => lodash.isNil(query) ||
|
|
7945
|
-
diffFilterNames.some(diffFilterName => {
|
|
7946
|
-
// Плейсхолдер параметра может ссылаться на фильтр целиком (`%name`)
|
|
7947
|
-
// либо на его свойство — уровень tree-фильтра или границу range
|
|
7948
|
-
// (`%name.lN`, `%name.min`, `%name.max`). Граница `.` обязательна,
|
|
7949
|
-
// иначе `%name` ложно матчит `%name2.l1`.
|
|
7950
|
-
const placeholder = `${FILTER_PREFIX}${diffFilterName}`;
|
|
7951
|
-
return Object.keys(parameters).some(paramName => {
|
|
7952
|
-
const paramValue = parameters[paramName];
|
|
7953
|
-
return (typeof paramValue === "string" &&
|
|
7954
|
-
(paramValue === placeholder || paramValue.startsWith(`${placeholder}.`)));
|
|
7955
|
-
});
|
|
7956
|
-
}));
|
|
7957
|
-
if (!filteredDataSources?.length) {
|
|
8193
|
+
const filteredDataSources = selectUpdatingDataSources({
|
|
8194
|
+
configDataSources,
|
|
8195
|
+
filters,
|
|
8196
|
+
configFilters,
|
|
8197
|
+
dashboard,
|
|
8198
|
+
prev: {
|
|
8199
|
+
filters: prevFilters.current,
|
|
8200
|
+
configFilters: prevConfigFilters.current,
|
|
8201
|
+
dashboardFilters: prevDashboardFilters.current,
|
|
8202
|
+
},
|
|
8203
|
+
});
|
|
8204
|
+
if (!filteredDataSources) {
|
|
7958
8205
|
return;
|
|
7959
8206
|
}
|
|
7960
8207
|
prevFilters.current = JSON.parse(JSON.stringify(filters));
|
|
8208
|
+
prevDashboardFilters.current = JSON.parse(JSON.stringify(dashboard?.selectedFilters ?? {}));
|
|
8209
|
+
prevConfigFilters.current = configFilters;
|
|
7961
8210
|
return filteredDataSources;
|
|
7962
|
-
}, [configDataSources, configFilters, filters]);
|
|
8211
|
+
}, [configDataSources, configFilters, dashboard, filters]);
|
|
7963
8212
|
const getUpdatedDataSources = React.useCallback((responses, currentDataSources, otherDataSources) => {
|
|
7964
8213
|
const newDataSources = JSON.parse(JSON.stringify([...currentDataSources, ...otherDataSources]));
|
|
7965
8214
|
responses.forEach((response, index) => {
|
|
@@ -8301,8 +8550,9 @@ const useExportPdf = (id, margin = 20) => {
|
|
|
8301
8550
|
const useSavePrototypeBuilder = () => {
|
|
8302
8551
|
const { ewktGeometry } = useGlobalContext();
|
|
8303
8552
|
const { layerInfo, attributes, dataSources, filters: selectedFilters, } = useWidgetContext(exports.WidgetType.FeatureCard);
|
|
8304
|
-
const { projectInfo
|
|
8553
|
+
const { projectInfo } = useWidgetContext(exports.WidgetType.Dashboard);
|
|
8305
8554
|
const { currentPage } = useWidgetPage(exports.WidgetType.FeatureCard);
|
|
8555
|
+
const dashboard = useDashboardFilterStore(exports.WidgetType.FeatureCard);
|
|
8306
8556
|
return React.useCallback((hook, input) => {
|
|
8307
8557
|
const resolvedParameters = applyQueryFilters({
|
|
8308
8558
|
parameters: hook.parameters ?? {},
|
|
@@ -8312,7 +8562,7 @@ const useSavePrototypeBuilder = () => {
|
|
|
8312
8562
|
attributes,
|
|
8313
8563
|
layerInfo,
|
|
8314
8564
|
dataSources,
|
|
8315
|
-
|
|
8565
|
+
dashboard,
|
|
8316
8566
|
});
|
|
8317
8567
|
const scriptParameters = {
|
|
8318
8568
|
projectName: projectInfo?.name,
|
|
@@ -8345,10 +8595,10 @@ const useSavePrototypeBuilder = () => {
|
|
|
8345
8595
|
}, [
|
|
8346
8596
|
attributes,
|
|
8347
8597
|
currentPage?.filters,
|
|
8598
|
+
dashboard,
|
|
8348
8599
|
dataSources,
|
|
8349
8600
|
ewktGeometry,
|
|
8350
8601
|
layerInfo,
|
|
8351
|
-
projectDataSources,
|
|
8352
8602
|
projectInfo?.name,
|
|
8353
8603
|
selectedFilters,
|
|
8354
8604
|
]);
|
|
@@ -8570,6 +8820,7 @@ const useRenderElement = (type = exports.WidgetType.Dashboard, elementConfig) =>
|
|
|
8570
8820
|
const { selectedTabId, setSelectedTabId, expandedContainers, layerInfo, attributes } = useWidgetContext(type);
|
|
8571
8821
|
const { config } = useWidgetConfig(type);
|
|
8572
8822
|
const { pageIndex } = useWidgetPage(type);
|
|
8823
|
+
const configStringSources = useConfigStringSources(type);
|
|
8573
8824
|
return React.useMemo(() => getRenderElement({
|
|
8574
8825
|
t,
|
|
8575
8826
|
type,
|
|
@@ -8581,9 +8832,11 @@ const useRenderElement = (type = exports.WidgetType.Dashboard, elementConfig) =>
|
|
|
8581
8832
|
pageIndex,
|
|
8582
8833
|
selectedTabId,
|
|
8583
8834
|
setSelectedTabId,
|
|
8835
|
+
configStringSources,
|
|
8584
8836
|
}), [
|
|
8585
8837
|
attributes,
|
|
8586
8838
|
config,
|
|
8839
|
+
configStringSources,
|
|
8587
8840
|
elementConfig,
|
|
8588
8841
|
expandedContainers,
|
|
8589
8842
|
layerInfo,
|
|
@@ -9087,9 +9340,8 @@ const LinkDialogContent = styled.div.withConfig({ displayName: "LinkDialogConten
|
|
|
9087
9340
|
* Заглушка недоступного изображения.
|
|
9088
9341
|
*
|
|
9089
9342
|
* Масштаб значка считается от размера самого превью: `font-size` контейнера приравнен к нему, а
|
|
9090
|
-
* бокс значка и его глиф заданы одной долей от этого размера.
|
|
9091
|
-
*
|
|
9092
|
-
* бокса и съезжал относительно квадрата.
|
|
9343
|
+
* бокс значка и его глиф заданы одной долей от этого размера. Фиксированный глиф в мелком превью
|
|
9344
|
+
* (`1.5rem` в списке) вышел бы крупнее своего бокса и съехал относительно квадрата.
|
|
9093
9345
|
*/
|
|
9094
9346
|
const ImagePreviewError = styled.div.withConfig({ displayName: "ImagePreviewError", componentId: "sc-7cwrhm" }) `
|
|
9095
9347
|
display: flex;
|
|
@@ -9661,6 +9913,7 @@ const DataSourceInnerContainer = React.memo(({ config, elementConfig, feature, m
|
|
|
9661
9913
|
valueElement?.attributeName,
|
|
9662
9914
|
]);
|
|
9663
9915
|
const { hasAnyFilter, isFiltered, onFilter } = useWidgetFilters(type, filterName, data);
|
|
9916
|
+
const configStringSources = useConfigStringSources(type);
|
|
9664
9917
|
const render = React.useMemo(() => getRenderElement({
|
|
9665
9918
|
config,
|
|
9666
9919
|
elementConfig,
|
|
@@ -9671,7 +9924,19 @@ const DataSourceInnerContainer = React.memo(({ config, elementConfig, feature, m
|
|
|
9671
9924
|
setSelectedTabId,
|
|
9672
9925
|
pageIndex,
|
|
9673
9926
|
type,
|
|
9674
|
-
|
|
9927
|
+
configStringSources,
|
|
9928
|
+
}), [
|
|
9929
|
+
config,
|
|
9930
|
+
elementConfig,
|
|
9931
|
+
attributes,
|
|
9932
|
+
layerInfo,
|
|
9933
|
+
expandedContainers,
|
|
9934
|
+
selectedTabId,
|
|
9935
|
+
setSelectedTabId,
|
|
9936
|
+
pageIndex,
|
|
9937
|
+
type,
|
|
9938
|
+
configStringSources,
|
|
9939
|
+
]);
|
|
9675
9940
|
if (!InnerContainer) {
|
|
9676
9941
|
return null;
|
|
9677
9942
|
}
|
|
@@ -10024,8 +10289,8 @@ const TreeFilterContainer = styled.div.withConfig({ displayName: "TreeFilterCont
|
|
|
10024
10289
|
}
|
|
10025
10290
|
`;
|
|
10026
10291
|
const TextFilterContainer = styled.div.withConfig({ displayName: "TextFilterContainer", componentId: "sc-1cbkxpd" }) `
|
|
10027
|
-
/* Ширину задаёт контейнер
|
|
10028
|
-
|
|
10292
|
+
/* Ширину задаёт контейнер фильтров: своя фиксированная ширина сделала бы текстовый фильтр
|
|
10293
|
+
уже соседних. */
|
|
10029
10294
|
width: 100%;
|
|
10030
10295
|
|
|
10031
10296
|
${uilibGl.MultiSelectContainer} {
|
|
@@ -11249,6 +11514,7 @@ const PagesContainer = React.memo(({ type = exports.WidgetType.Dashboard, noBord
|
|
|
11249
11514
|
const { config } = useWidgetConfig(type);
|
|
11250
11515
|
const { pageIndex, currentPage } = useWidgetPage(type);
|
|
11251
11516
|
const { selectedTabId, setSelectedTabId, expandedContainers, attributes, onContainerChange } = useWidgetContext(type);
|
|
11517
|
+
const configStringSources = useConfigStringSources(type);
|
|
11252
11518
|
const { options } = config || {};
|
|
11253
11519
|
const { column, width } = options || {};
|
|
11254
11520
|
const isColumn = column === undefined || column;
|
|
@@ -11266,12 +11532,13 @@ const PagesContainer = React.memo(({ type = exports.WidgetType.Dashboard, noBord
|
|
|
11266
11532
|
selectedTabId,
|
|
11267
11533
|
setSelectedTabId,
|
|
11268
11534
|
onChange: onContainerChange,
|
|
11535
|
+
configStringSources,
|
|
11269
11536
|
}), [
|
|
11270
11537
|
attributes,
|
|
11271
11538
|
config,
|
|
11539
|
+
configStringSources,
|
|
11272
11540
|
currentPage,
|
|
11273
11541
|
expandedContainers,
|
|
11274
|
-
getRenderElement,
|
|
11275
11542
|
onContainerChange,
|
|
11276
11543
|
pageIndex,
|
|
11277
11544
|
selectedTabId,
|
|
@@ -11371,8 +11638,8 @@ const TINTED_BACKGROUND_PERCENT = 6;
|
|
|
11371
11638
|
/** Тонированный фон из цвета-источника; без цвета — `undefined`, чтобы уступить следующему в приоритете. */
|
|
11372
11639
|
const tintedBackground = (color) => color ? transparentizeColor(color, TINTED_BACKGROUND_PERCENT) : undefined;
|
|
11373
11640
|
/**
|
|
11374
|
-
* Отступ между элементами плитки: заданный в конфиге `innerGap` (px) либо
|
|
11375
|
-
*
|
|
11641
|
+
* Отступ между элементами плитки: заданный в конфиге `innerGap` (px) либо собственный отступ
|
|
11642
|
+
* элемента по умолчанию, если опция не задана.
|
|
11376
11643
|
*/
|
|
11377
11644
|
const innerGapOr = (fallback) => ({ $innerGap }) => ($innerGap == null ? fallback : `${$innerGap}px`);
|
|
11378
11645
|
/**
|
|
@@ -11486,7 +11753,7 @@ const RoundedBackgroundContainerWrapper = styled(uilibGl.Flex).withConfig({ disp
|
|
|
11486
11753
|
text-align: left;
|
|
11487
11754
|
}
|
|
11488
11755
|
|
|
11489
|
-
/* Зазор наружу даёт обёртка иконки, здесь —
|
|
11756
|
+
/* Зазор наружу даёт обёртка иконки, здесь — отступ внутри её бокса. */
|
|
11490
11757
|
span[kind] {
|
|
11491
11758
|
margin-right: 0.5rem;
|
|
11492
11759
|
}
|
|
@@ -11506,7 +11773,7 @@ const RoundedBackgroundContainerWrapper = styled(uilibGl.Flex).withConfig({ disp
|
|
|
11506
11773
|
|
|
11507
11774
|
${ContainerUnits} {
|
|
11508
11775
|
margin-left: ${({ $inlineUnits, $innerGap }) => ($inlineUnits ? innerGapOr("0.25rem")({ $innerGap }) : 0)};
|
|
11509
|
-
/* Единицы под значением:
|
|
11776
|
+
/* Единицы под значением: без innerGap зазора нет. */
|
|
11510
11777
|
margin-top: ${({ $inlineUnits, $innerGap }) => (!$inlineUnits && $innerGap != null ? `${$innerGap}px` : 0)};
|
|
11511
11778
|
}
|
|
11512
11779
|
|
|
@@ -11549,17 +11816,17 @@ const RoundedBackgroundContainer = React.memo(({ type, elementConfig, feature, r
|
|
|
11549
11816
|
const color = React.useMemo(() => attributes?.find(({ attributeName }) => attributeName === colorAttribute)?.value || fontColor, [attributes, colorAttribute, fontColor]);
|
|
11550
11817
|
/**
|
|
11551
11818
|
* Цвет иконки — только источник фона: сам глиф красит ElementIcon/ElementSvg своим `fontColor`.
|
|
11552
|
-
*
|
|
11819
|
+
* `style.color` читается фолбэком — тем же порядком, что и в редакторе.
|
|
11553
11820
|
*/
|
|
11554
11821
|
const iconColor = React.useMemo(() => iconElement?.options?.fontColor || iconElement?.style?.color, [iconElement]);
|
|
11555
11822
|
/**
|
|
11556
11823
|
* Отступ иконки задаётся на её обёртке `ContainerIconWrapper`: сам элемент `icon` рендерится через
|
|
11557
11824
|
* ElementIcon/ElementSvg со своими styled-компонентами, до него из стилей плитки не дотянуться.
|
|
11558
11825
|
*
|
|
11559
|
-
* В `big` иконка и текст стоят в строку — зазор горизонтальный (
|
|
11560
|
-
* В колоночной плитке зазор
|
|
11826
|
+
* В `big` иконка и текст стоят в строку — зазор горизонтальный (по умолчанию 0.5rem).
|
|
11827
|
+
* В колоночной плитке зазор вертикальный и по умолчанию нулевой: `mb` появляется только
|
|
11561
11828
|
* с заданным `innerGap`. `mr` там на вертикаль не влияет, но участвует в горизонтальном
|
|
11562
|
-
* центрировании, поэтому
|
|
11829
|
+
* центрировании, поэтому от `innerGap` не зависит — иначе он сдвигал бы иконку вбок.
|
|
11563
11830
|
*/
|
|
11564
11831
|
const renderIcon = React.useMemo(() => {
|
|
11565
11832
|
if (!iconElement)
|
|
@@ -11605,6 +11872,7 @@ const SlideshowContainer = React.memo(({ config, elementConfig, type }) => {
|
|
|
11605
11872
|
elementConfig,
|
|
11606
11873
|
dataSources,
|
|
11607
11874
|
});
|
|
11875
|
+
const configStringSources = useConfigStringSources(type);
|
|
11608
11876
|
const { id, options } = elementConfig || {};
|
|
11609
11877
|
const { expandable, expanded } = options || {};
|
|
11610
11878
|
const isVisible = isVisibleContainer(id, expandable, expanded, expandedContainers);
|
|
@@ -11619,9 +11887,11 @@ const SlideshowContainer = React.memo(({ config, elementConfig, type }) => {
|
|
|
11619
11887
|
setSelectedTabId,
|
|
11620
11888
|
pageIndex,
|
|
11621
11889
|
type,
|
|
11890
|
+
configStringSources,
|
|
11622
11891
|
}), [
|
|
11623
11892
|
getRenderElement,
|
|
11624
11893
|
config,
|
|
11894
|
+
configStringSources,
|
|
11625
11895
|
elementConfig,
|
|
11626
11896
|
attributes,
|
|
11627
11897
|
layerInfo,
|
|
@@ -11861,7 +12131,7 @@ const useStructuredDataDraft = ({ schema, dataSource, hasDataSource, filterValue
|
|
|
11861
12131
|
}, []);
|
|
11862
12132
|
const reset = React.useCallback(() => setRows(baseline.current), []);
|
|
11863
12133
|
const commitSaved = React.useCallback((next) => {
|
|
11864
|
-
// Сохранённое состояние — наше: значение фильтра
|
|
12134
|
+
// Сохранённое состояние — наше: значение фильтра записали мы, и вернувшееся из него
|
|
11865
12135
|
// обновление не должно пересоздавать ключи уже показанных строк.
|
|
11866
12136
|
hydrated.current = true;
|
|
11867
12137
|
applyBaseline(next);
|
|
@@ -13224,8 +13494,8 @@ const useVoteContainer = (elementConfig) => {
|
|
|
13224
13494
|
const VoteContainer = React.memo(({ type, elementConfig, isVisible, renderElement }) => {
|
|
13225
13495
|
const { root, body } = useContainerRoot({ elementConfig });
|
|
13226
13496
|
const { configured, ...vote } = useVoteContainer(elementConfig);
|
|
13227
|
-
// Без имени атрибута или хотя бы одного источника контейнеру не с чем
|
|
13228
|
-
//
|
|
13497
|
+
// Без имени атрибута или хотя бы одного источника контейнеру не с чем работать. Ошибка вместо
|
|
13498
|
+
// пустоты: молчаливый `null` выдавал бы опечатку в конфиге за «блока просто нет».
|
|
13229
13499
|
if (!configured) {
|
|
13230
13500
|
return jsxRuntime.jsx(DataSourceError, { name: elementConfig?.templateName ?? exports.ContainerTemplate.Vote });
|
|
13231
13501
|
}
|
|
@@ -14071,8 +14341,8 @@ const useTaskNotifications = ({ enabled, suppressed, status, lastMessage, openLo
|
|
|
14071
14341
|
const TaskContainer = React.memo(({ type, elementConfig, renderElement }) => {
|
|
14072
14342
|
const { t, ewktGeometry } = useGlobalContext();
|
|
14073
14343
|
const { dataSources, filters: selectedFilters, attributes, layerInfo, changeFilters, } = useWidgetContext(type);
|
|
14074
|
-
const { dataSources: projectDataSources } = useWidgetContext(exports.WidgetType.Dashboard);
|
|
14075
14344
|
const { currentPage } = useWidgetPage(type);
|
|
14345
|
+
const dashboard = useDashboardFilterStore(type);
|
|
14076
14346
|
const { taskId, runTask, stopTask, status, openLog, loading, isLogDialogOpen, closeLog, log, lastMessage, result, } = usePythonTask();
|
|
14077
14347
|
const { options } = elementConfig || {};
|
|
14078
14348
|
const { title, relatedResources, center, icon, statusColors, responseFilters, useNotifications, } = options || {};
|
|
@@ -14111,11 +14381,11 @@ const TaskContainer = React.memo(({ type, elementConfig, renderElement }) => {
|
|
|
14111
14381
|
attributes,
|
|
14112
14382
|
layerInfo,
|
|
14113
14383
|
dataSources,
|
|
14114
|
-
|
|
14384
|
+
dashboard,
|
|
14115
14385
|
});
|
|
14116
14386
|
return runTask({ resourceId, parameters: newParams, script, fileName, methodName });
|
|
14117
14387
|
}));
|
|
14118
|
-
}, [attributes, currentPage.filters, dataSources, ewktGeometry, layerInfo,
|
|
14388
|
+
}, [attributes, currentPage.filters, dashboard, dataSources, ewktGeometry, layerInfo, relatedResources, runTask, selectedFilters, stopTask, taskId]);
|
|
14119
14389
|
return (jsxRuntime.jsxs(ContainerRoot, { ...root, children: [jsxRuntime.jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsxRuntime.jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), jsxRuntime.jsxs(TaskContainerWrapper, { ...body, justifyContent: center ? "center" : "flex-start", children: [jsxRuntime.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) && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(uilibGl.IconButton, { kind: "info", onClick: openLog }), jsxRuntime.jsx(LogDialog, { logs: log, status: status, statusColors: statusColors, isOpen: isLogDialogOpen, onClose: onCloseLog, onMinimize: useNotifications ? onMinimizeLog : undefined })] }))] })] }));
|
|
14120
14390
|
});
|
|
14121
14391
|
|
|
@@ -14517,7 +14787,7 @@ const EditAttachmentContainer = React.memo(({ type, elementConfig, renderElement
|
|
|
14517
14787
|
return (jsxRuntime.jsxs(AttachmentsContainer, { id: id, style: { ...BASE_CONTAINER_STYLE, ...style }, ...bgHost, children: [jsxRuntime.jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsxRuntime.jsx(AttachmentsHeader, { alias: renderElement?.({ id: "alias" }), count: items.length, viewMode: viewMode, onChangeViewMode: setViewMode }), jsxRuntime.jsx(AttachmentsContent, { children: viewMode === "grid" ? (jsxRuntime.jsx(AttachmentsGrid, { items: visibleItems, isEdit: true, onPreview: handlePreview, onDelete: handleDelete })) : (jsxRuntime.jsx(AttachmentsList, { items: visibleItems, isEdit: true, onPreview: handlePreview, onDelete: handleDelete })) }), hasMore && !showMore && (jsxRuntime.jsx(ShowMoreButton, { hiddenCount: hiddenCount, onClick: handleShowMore })), jsxRuntime.jsx(AddButton, { accept: fileExtensions, onSelectFiles: uploading ? () => undefined : handleUpload, onSelectFromCatalog: handleSelectFromCatalog, onSelectFromLink: handleOpenLinkDialog }), jsxRuntime.jsx(AttachmentLinkDialog, { isOpen: isLinkDialogOpen, onClose: handleCloseLinkDialog, onSubmit: handleAddByLink }), previewIndex !== null && (jsxRuntime.jsx(uilibGl.Preview, { images: previewImages, initialIndex: previewIndex, isOpen: previewIndex !== null, onClose: handleClosePreview, onDownload: handleDownload }, previewIndex))] }));
|
|
14518
14788
|
});
|
|
14519
14789
|
|
|
14520
|
-
// `ProgressContainer` и `RoundedBackgroundContainer`
|
|
14790
|
+
// `ProgressContainer` и `RoundedBackgroundContainer` принимают `InnerContainerProps`
|
|
14521
14791
|
// (см. `DataSourceInnerContainer/types.ts`). `<Name>ContainerProps` ⊆ `InnerContainerProps`
|
|
14522
14792
|
// по структуре — каст оправдан, расхождение только в generic-параметре `propTypes`.
|
|
14523
14793
|
const ProgressContainerTyped = ProgressContainer;
|
|
@@ -15556,10 +15826,25 @@ const ElementChips = React.memo(({ type, elementConfig }) => {
|
|
|
15556
15826
|
return (jsxRuntime.jsx(DashboardChipsContainer, { style: style, children: tags.map(tag => (jsxRuntime.jsx(DashboardChip$1, { text: tag.text, "$bgColor": tag.color || colorAttr?.value || bgColor, "$fontColor": fontColor, "$fontSize": fontSize, "$isDefault": !tag.color && !colorAttr?.value }, tag.text))) }));
|
|
15557
15827
|
});
|
|
15558
15828
|
|
|
15829
|
+
/**
|
|
15830
|
+
* Первое непустое значение за жизнь компонента. Дефолт контрола с подстановкой
|
|
15831
|
+
* (`$dashboard:scenario`) фиксируется при открытии карточки: пока фильтры дашборда грузятся, он ещё
|
|
15832
|
+
* пуст, а после — смена фильтра дашборда не должна затирать выбор.
|
|
15833
|
+
*/
|
|
15834
|
+
const useFirstNonEmptyValue = (value) => {
|
|
15835
|
+
const firstValue = React.useRef(value);
|
|
15836
|
+
if (isEmptyValue(firstValue.current) && !isEmptyValue(value)) {
|
|
15837
|
+
firstValue.current = value;
|
|
15838
|
+
}
|
|
15839
|
+
return firstValue.current;
|
|
15840
|
+
};
|
|
15841
|
+
|
|
15559
15842
|
const ElementControl = ({ elementConfig }) => {
|
|
15560
15843
|
const { t } = useGlobalContext();
|
|
15561
15844
|
const { attributes, layerInfo, dataSources, controls, changeControls } = useWidgetContext(exports.WidgetType.FeatureCard);
|
|
15562
15845
|
const { options, attributeName, defaultValue } = elementConfig || {};
|
|
15846
|
+
// Дефолт фиксируется при открытии карточки и в `controls` не пишется — только показ.
|
|
15847
|
+
const initialDefaultValue = useFirstNonEmptyValue(defaultValue);
|
|
15563
15848
|
const { relatedDataSource, label, width, control, placeholder = t("selectValue", {
|
|
15564
15849
|
ns: "dashboard",
|
|
15565
15850
|
defaultValue: "Выберите значение",
|
|
@@ -15589,7 +15874,7 @@ const ElementControl = ({ elementConfig }) => {
|
|
|
15589
15874
|
}, [control?.targetAttributeName, layerInfo?.configuration]);
|
|
15590
15875
|
const currentValue = controls?.[control?.targetAttributeName] ??
|
|
15591
15876
|
attribute?.value ??
|
|
15592
|
-
|
|
15877
|
+
initialDefaultValue;
|
|
15593
15878
|
const handleChange = React.useCallback(([option]) => {
|
|
15594
15879
|
if (control?.targetAttributeName && changeControls) {
|
|
15595
15880
|
changeControls({
|
|
@@ -17325,8 +17610,8 @@ const getNextSort = (current, attributeName) => {
|
|
|
17325
17610
|
* Css собственного бокса таблицы: размер из `options` представления плюс прокрутка того,
|
|
17326
17611
|
* что в этот размер не влез.
|
|
17327
17612
|
*
|
|
17328
|
-
* Размеров нет — нет и бокса: таблица занимает столько, сколько просит содержимое, а
|
|
17329
|
-
*
|
|
17613
|
+
* Размеров нет — нет и бокса: таблица занимает столько, сколько просит содержимое, а лишнее
|
|
17614
|
+
* уходит в прокрутку области представления контейнера.
|
|
17330
17615
|
*
|
|
17331
17616
|
* Проценты работают только по ширине. Между областью представления и таблицей стоит блочная
|
|
17332
17617
|
* обёртка элемента (`ElementValueWrapper`) с высотой `auto`, и `height: "100%"` в ней не
|
|
@@ -17596,8 +17881,8 @@ const getDataFromRelatedFeatures = ({ t, config, filters, relatedConfig, dataSou
|
|
|
17596
17881
|
}
|
|
17597
17882
|
// Источник чарта отличается от источника фильтра наличием осей: BarChartFilter собирает
|
|
17598
17883
|
// relatedConfig вручную и оси не задаёт, потому что ему нужна исходная палитра, а не растянутая
|
|
17599
|
-
// под число элементов. Гейт смотрит и на устаревший chartAxis — иначе у
|
|
17600
|
-
// молча поехали бы цвета столбцов и долей.
|
|
17884
|
+
// под число элементов. Гейт смотрит и на устаревший chartAxis — иначе у конфигов с плоской
|
|
17885
|
+
// формой оси молча поехали бы цвета столбцов и долей.
|
|
17601
17886
|
const gradientArray = hasAxisConfig(relatedConfig)
|
|
17602
17887
|
? stretchPalette(colors, data.length)
|
|
17603
17888
|
: colors;
|
|
@@ -17624,7 +17909,7 @@ const getDataFromRelatedFeatures = ({ t, config, filters, relatedConfig, dataSou
|
|
|
17624
17909
|
min: filter?.attributeMin
|
|
17625
17910
|
? feature.properties[filter.attributeMin]
|
|
17626
17911
|
: null,
|
|
17627
|
-
max: filter?.
|
|
17912
|
+
max: filter?.attributeMax
|
|
17628
17913
|
? feature.properties[filter.attributeMax]
|
|
17629
17914
|
: null,
|
|
17630
17915
|
},
|
|
@@ -17994,7 +18279,7 @@ const isContainerRef = (element) => "containerId" in element &&
|
|
|
17994
18279
|
!("templateName" in element) &&
|
|
17995
18280
|
!("type" in element) &&
|
|
17996
18281
|
!("children" in element);
|
|
17997
|
-
const getRenderElement = ({ t, config, elementConfig, attributes = [], layerInfo, selectedTabId, setSelectedTabId, pageIndex, expandedContainers, onChange, type = exports.WidgetType.FeatureCard, }) => (props) => {
|
|
18282
|
+
const getRenderElement = ({ t, config, elementConfig, attributes = [], layerInfo, selectedTabId, setSelectedTabId, pageIndex, expandedContainers, onChange, type = exports.WidgetType.FeatureCard, configStringSources, }) => (props) => {
|
|
17998
18283
|
const { id, index, wrap = true } = props;
|
|
17999
18284
|
const currentConfig = elementConfig || config;
|
|
18000
18285
|
let element = id
|
|
@@ -18011,6 +18296,12 @@ const getRenderElement = ({ t, config, elementConfig, attributes = [], layerInfo
|
|
|
18011
18296
|
container = container[0];
|
|
18012
18297
|
element = container || undefined;
|
|
18013
18298
|
}
|
|
18299
|
+
// Строки узла — от атрибутов этого рендера: у строк `DataSource` они свои, не карточки.
|
|
18300
|
+
element = resolveElementStrings(element, configStringSources && {
|
|
18301
|
+
...configStringSources,
|
|
18302
|
+
attributes: attributes.length ? attributes : configStringSources.attributes,
|
|
18303
|
+
layerInfo: layerInfo ?? configStringSources.layerInfo,
|
|
18304
|
+
});
|
|
18014
18305
|
const { options, children, templateName } = element || {};
|
|
18015
18306
|
const { tabId, hideEmpty, expandable, expanded, innerTemplateName } = options || {};
|
|
18016
18307
|
if (isNotValidSelectedTab(tabId, selectedTabId))
|
|
@@ -18027,6 +18318,7 @@ const getRenderElement = ({ t, config, elementConfig, attributes = [], layerInfo
|
|
|
18027
18318
|
pageIndex,
|
|
18028
18319
|
onChange,
|
|
18029
18320
|
type,
|
|
18321
|
+
configStringSources,
|
|
18030
18322
|
});
|
|
18031
18323
|
// Ставится внутри `getDefaultContainer` — единственного места, которое знает по построению,
|
|
18032
18324
|
// что значение оказалось контейнером. Коллбэк вызывается синхронно, читаем строго после.
|
|
@@ -18073,7 +18365,12 @@ const getRenderElement = ({ t, config, elementConfig, attributes = [], layerInfo
|
|
|
18073
18365
|
const useModalContent = (type, modalId) => {
|
|
18074
18366
|
const { config } = useWidgetConfig(type);
|
|
18075
18367
|
const { expandedContainers, attributes } = useWidgetContext(type);
|
|
18076
|
-
const
|
|
18368
|
+
const configStringSources = useConfigStringSources(type);
|
|
18369
|
+
// Заголовок модалки (`options.title`) — тоже строка конфига с подстановками.
|
|
18370
|
+
const modalConfig = React.useMemo(() => {
|
|
18371
|
+
const modal = (config?.modals ?? []).find(({ id }) => id === modalId) ?? null;
|
|
18372
|
+
return resolveElementStrings(modal, configStringSources);
|
|
18373
|
+
}, [config?.modals, configStringSources, modalId]);
|
|
18077
18374
|
const modalContent = React.useMemo(() => (modalConfig?.children ?? []), [modalConfig]);
|
|
18078
18375
|
const modalSlots = React.useMemo(() => modalContent.filter(({ id }) => MODAL_SLOT_IDS.includes(id)), [modalContent]);
|
|
18079
18376
|
const renderElement = React.useMemo(() => getRenderElement({
|
|
@@ -18082,14 +18379,16 @@ const useModalContent = (type, modalId) => {
|
|
|
18082
18379
|
elementConfig: { children: modalContent },
|
|
18083
18380
|
attributes,
|
|
18084
18381
|
expandedContainers,
|
|
18085
|
-
|
|
18382
|
+
configStringSources,
|
|
18383
|
+
}), [type, config, modalContent, attributes, expandedContainers, configStringSources]);
|
|
18086
18384
|
const renderSlot = React.useMemo(() => getRenderElement({
|
|
18087
18385
|
type,
|
|
18088
18386
|
config,
|
|
18089
18387
|
elementConfig: { children: modalSlots },
|
|
18090
18388
|
attributes,
|
|
18091
18389
|
expandedContainers,
|
|
18092
|
-
|
|
18390
|
+
configStringSources,
|
|
18391
|
+
}), [type, config, modalSlots, attributes, expandedContainers, configStringSources]);
|
|
18093
18392
|
const hasTitleIcon = React.useMemo(() => modalSlots.some(({ id }) => id === MODAL_TITLE_ICON_SLOT_ID), [modalSlots]);
|
|
18094
18393
|
const hasBgImage = React.useMemo(() => hasContainerBgImage({ children: modalSlots }), [modalSlots]);
|
|
18095
18394
|
return { modalConfig, modalContent, renderElement, renderSlot, hasTitleIcon, hasBgImage };
|
|
@@ -18260,9 +18559,8 @@ const getFeatureCardHeader = (templateName) => {
|
|
|
18260
18559
|
/**
|
|
18261
18560
|
* Ширина фильтра из конфига. Без явного значения фильтр занимает всю ширину контейнера.
|
|
18262
18561
|
*
|
|
18263
|
-
*
|
|
18264
|
-
*
|
|
18265
|
-
* панели фильтры вставали «лесенкой» — и тем заметнее, чем шире контейнер.
|
|
18562
|
+
* Фолбэк общий для всех типов фильтров: свои пиксельные фолбэки у разных типов выстраивали бы
|
|
18563
|
+
* фильтры одной панели «лесенкой» — и тем заметнее, чем шире контейнер.
|
|
18266
18564
|
*/
|
|
18267
18565
|
const getFilterWidth = (width) => toCssSize(width) ?? "100%";
|
|
18268
18566
|
|
|
@@ -18749,7 +19047,7 @@ const RangeDateFilter = ({ type, filter }) => {
|
|
|
18749
19047
|
const maxDate = React.useMemo(() => maxValue || maxFromData?.toISOString(), [maxFromData, maxValue]);
|
|
18750
19048
|
// Диапазон по умолчанию: `defaultValue: [from, to]` из конфига фильтра — начальное значение
|
|
18751
19049
|
// и точка сброса. Границы календаря (min/max) при этом не меняются; незаданная граница
|
|
18752
|
-
// дефолта падает на них же — как
|
|
19050
|
+
// дефолта падает на них же — как и без `defaultValue`.
|
|
18753
19051
|
const defaultRange = React.useMemo(() => {
|
|
18754
19052
|
const [from, to] = (Array.isArray(configFilter?.defaultValue) ? configFilter.defaultValue : []);
|
|
18755
19053
|
return [getDate(from)?.toISOString() ?? minDate, getDate(to)?.toISOString() ?? maxDate];
|
|
@@ -19395,21 +19693,23 @@ const getFilterValue = ({ selectedFilters, configFilters, filterName, newValue,
|
|
|
19395
19693
|
/** Условие источника задаётся строкой или массивом строк — приводим к массиву для единообразного обхода. */
|
|
19396
19694
|
const toConditionsArray = (value) => Array.isArray(value) ? value : value ? [value] : [];
|
|
19397
19695
|
|
|
19398
|
-
|
|
19399
|
-
|
|
19400
|
-
|
|
19401
|
-
|
|
19696
|
+
/**
|
|
19697
|
+
* Строка ссылается на вид карты — `%extent` / `%zoom` целиком или внутри строки. Граница та же,
|
|
19698
|
+
* что у резолвера строк конфига: составные формы (`%zoomLevel`, `%extent_id`, `%zoom.min`)
|
|
19699
|
+
* принадлежат пользовательским фильтрам, не карте.
|
|
19700
|
+
*/
|
|
19701
|
+
const hasMapViewPlaceholder = (value) => typeof value === "string" &&
|
|
19702
|
+
tokenizeConfigString(value).some(segment => typeof segment !== "string" &&
|
|
19703
|
+
segment.kind === "filter" &&
|
|
19704
|
+
segment.scope === "widget" &&
|
|
19705
|
+
!segment.prop &&
|
|
19706
|
+
MAP_VIEW_FILTER_NAMES.includes(segment.name));
|
|
19402
19707
|
/**
|
|
19403
19708
|
* Источники, чей запрос зависит от текущего вида карты — в `parameters` или `condition` у них
|
|
19404
19709
|
* есть `%extent` / `%zoom`. Перезапрашивают при движении карты только их: иначе каждый пан
|
|
19405
19710
|
* дёргал бы все запросы страницы.
|
|
19406
19711
|
*/
|
|
19407
|
-
const getMapViewDataSources = (dataSources) => dataSources?.filter(({ parameters, condition }) => {
|
|
19408
|
-
const hasInParameters = Object.values(parameters ?? {}).some(value => typeof value === "string" && MAP_VIEW_PLACEHOLDERS.includes(value));
|
|
19409
|
-
if (hasInParameters)
|
|
19410
|
-
return true;
|
|
19411
|
-
return toConditionsArray(condition).some(hasMapViewPlaceholder);
|
|
19412
|
-
}) ?? [];
|
|
19712
|
+
const getMapViewDataSources = (dataSources) => dataSources?.filter(({ parameters, condition }) => Object.values(parameters ?? {}).some(hasMapViewPlaceholder) || toConditionsArray(condition).some(hasMapViewPlaceholder)) ?? [];
|
|
19413
19713
|
|
|
19414
19714
|
const getFormattedAttributes = (t, data, attributes, config) => {
|
|
19415
19715
|
const showOtherItems = config?.options?.otherItems < data?.length;
|
|
@@ -19530,8 +19830,6 @@ const getLayerInfo = (layer) => {
|
|
|
19530
19830
|
|
|
19531
19831
|
const getLayerInfoFromDataSources = (layerInfos, dataSources, relatedDataSource) => layerInfos?.find(({ name }) => name === dataSources?.find(item => item.name === relatedDataSource)?.layerName);
|
|
19532
19832
|
|
|
19533
|
-
const getPagesFromConfig = (config) => config?.children?.[0]?.children;
|
|
19534
|
-
|
|
19535
19833
|
const getPagesFromProjectInfo = (projectInfo) => getPagesFromConfig(projectInfo?.content?.dashboardConfiguration);
|
|
19536
19834
|
|
|
19537
19835
|
const getRelatedAttribute = (layerInfo, sourceAttributeName, relatedLayerName) => {
|
|
@@ -19649,6 +19947,27 @@ const removeDataSource = (dashboardConfiguration, name, pageIndex) => {
|
|
|
19649
19947
|
return newConfig;
|
|
19650
19948
|
};
|
|
19651
19949
|
|
|
19950
|
+
/**
|
|
19951
|
+
* Датасорсы, которые нужно перезапросить после изменения фильтров: их параметры ссылаются на
|
|
19952
|
+
* изменившийся фильтр виджета (`%name`, в том числе через дефолт `$dashboard:`) или дашборда
|
|
19953
|
+
* (`$dashboard:name`). Датасорсы слоя без `query` перезапрашиваются на любое изменение.
|
|
19954
|
+
* Нечего перезапрашивать — `undefined`.
|
|
19955
|
+
*/
|
|
19956
|
+
const selectUpdatingDataSources = ({ configDataSources, filters, configFilters, dashboard, prev, }) => {
|
|
19957
|
+
const widgetNames = [
|
|
19958
|
+
...getChangedFilterNames(filters, prev.filters ?? {}, configFilters),
|
|
19959
|
+
...getChangedDefaultNames(configFilters, prev.configFilters, filters),
|
|
19960
|
+
];
|
|
19961
|
+
const dashboardNames = dashboard
|
|
19962
|
+
? getChangedFilterNames(dashboard.selectedFilters, prev.dashboardFilters ?? {}, dashboard.configFilters)
|
|
19963
|
+
: [];
|
|
19964
|
+
if (!widgetNames.length && !dashboardNames.length)
|
|
19965
|
+
return undefined;
|
|
19966
|
+
const dataSources = configDataSources?.filter(({ parameters = {}, query }) => lodash.isNil(query) ||
|
|
19967
|
+
Object.values(parameters).some(value => referencesFilters({ value, widgetNames, dashboardNames, hasDashboardStore: !!dashboard })));
|
|
19968
|
+
return dataSources?.length ? dataSources : undefined;
|
|
19969
|
+
};
|
|
19970
|
+
|
|
19652
19971
|
const updateDataSource = (dashboardConfiguration, name, pageIndex, data) => {
|
|
19653
19972
|
const newConfig = JSON.parse(JSON.stringify(dashboardConfiguration));
|
|
19654
19973
|
if (newConfig.dataSources?.length) {
|
|
@@ -20956,6 +21275,9 @@ exports.CHART_TYPES = CHART_TYPES;
|
|
|
20956
21275
|
exports.COMPACT_FRACTION_DIGITS = COMPACT_FRACTION_DIGITS;
|
|
20957
21276
|
exports.CONFIG_PAGES_ID = CONFIG_PAGES_ID;
|
|
20958
21277
|
exports.CONFIG_PAGE_ID = CONFIG_PAGE_ID;
|
|
21278
|
+
exports.CONFIG_STRING_OPTION_KEYS = CONFIG_STRING_OPTION_KEYS;
|
|
21279
|
+
exports.CONFIG_TEXT_ARRAY_SEPARATOR = CONFIG_TEXT_ARRAY_SEPARATOR;
|
|
21280
|
+
exports.CONFIG_TOKEN_REGEXP = CONFIG_TOKEN_REGEXP;
|
|
20959
21281
|
exports.CONTAINERS_GROUP_DEFAULTS = CONTAINERS_GROUP_DEFAULTS;
|
|
20960
21282
|
exports.CONTAINER_BODY_ATTRIBUTE = CONTAINER_BODY_ATTRIBUTE;
|
|
20961
21283
|
exports.CONTAINER_BODY_FILL_STYLE = CONTAINER_BODY_FILL_STYLE;
|
|
@@ -21157,6 +21479,7 @@ exports.PresentationPanelWrapper = PresentationPanelWrapper;
|
|
|
21157
21479
|
exports.PresentationWrapper = PresentationWrapper;
|
|
21158
21480
|
exports.ProgressContainer = ProgressContainer;
|
|
21159
21481
|
exports.QUERY_DESCRIPTION_CACHE_TTL = QUERY_DESCRIPTION_CACHE_TTL;
|
|
21482
|
+
exports.RANGE_PROPS = RANGE_PROPS;
|
|
21160
21483
|
exports.RESIZE_HANDLE_ATTR = RESIZE_HANDLE_ATTR;
|
|
21161
21484
|
exports.ResizeHandle = ResizeHandle;
|
|
21162
21485
|
exports.RoundedBackgroundContainer = RoundedBackgroundContainer;
|
|
@@ -21194,7 +21517,6 @@ exports.ZOOM_FILTER_NAME = ZOOM_FILTER_NAME;
|
|
|
21194
21517
|
exports.addDataSource = addDataSource;
|
|
21195
21518
|
exports.addDataSources = addDataSources;
|
|
21196
21519
|
exports.adjustColor = adjustColor;
|
|
21197
|
-
exports.applyFiltersToCondition = applyFiltersToCondition;
|
|
21198
21520
|
exports.applyQueryFilters = applyQueryFilters;
|
|
21199
21521
|
exports.applyTreeFilterToCondition = applyTreeFilterToCondition;
|
|
21200
21522
|
exports.applyVarsToCondition = applyVarsToCondition;
|
|
@@ -21233,6 +21555,8 @@ exports.findCellContext = findCellContext;
|
|
|
21233
21555
|
exports.formatArea = formatArea;
|
|
21234
21556
|
exports.formatAttributeValue = formatAttributeValue;
|
|
21235
21557
|
exports.formatChartRelatedValue = formatChartRelatedValue;
|
|
21558
|
+
exports.formatConditionSection = formatConditionSection;
|
|
21559
|
+
exports.formatConditionString = formatConditionString;
|
|
21236
21560
|
exports.formatConditionValue = formatConditionValue;
|
|
21237
21561
|
exports.formatDataSourceCondition = formatDataSourceCondition;
|
|
21238
21562
|
exports.formatDate = formatDate$1;
|
|
@@ -21250,6 +21574,8 @@ exports.getAttributeValue = getAttributeValue;
|
|
|
21250
21574
|
exports.getAttributesConfiguration = getAttributesConfiguration;
|
|
21251
21575
|
exports.getAverageTrackSize = getAverageTrackSize;
|
|
21252
21576
|
exports.getAxisSide = getAxisSide;
|
|
21577
|
+
exports.getChangedDefaultNames = getChangedDefaultNames;
|
|
21578
|
+
exports.getChangedFilterNames = getChangedFilterNames;
|
|
21253
21579
|
exports.getChartAxes = getChartAxes;
|
|
21254
21580
|
exports.getChartFilterName = getChartFilterName;
|
|
21255
21581
|
exports.getChartMarkers = getChartMarkers;
|
|
@@ -21269,6 +21595,7 @@ exports.getFeatureAttributes = getFeatureAttributes;
|
|
|
21269
21595
|
exports.getFeatureCardHeader = getFeatureCardHeader;
|
|
21270
21596
|
exports.getFilterComponent = getFilterComponent;
|
|
21271
21597
|
exports.getFilterSelectedItems = getFilterSelectedItems;
|
|
21598
|
+
exports.getFilterTokenValue = getFilterTokenValue;
|
|
21272
21599
|
exports.getFilterValue = getFilterValue;
|
|
21273
21600
|
exports.getFormattedAttributes = getFormattedAttributes;
|
|
21274
21601
|
exports.getGradientColors = getGradientColors;
|
|
@@ -21280,6 +21607,7 @@ exports.getLayerInfoFromDataSources = getLayerInfoFromDataSources;
|
|
|
21280
21607
|
exports.getLayoutChildren = getLayoutChildren;
|
|
21281
21608
|
exports.getMapViewDataSources = getMapViewDataSources;
|
|
21282
21609
|
exports.getModalsDataSources = getModalsDataSources;
|
|
21610
|
+
exports.getPageFilters = getPageFilters;
|
|
21283
21611
|
exports.getPagesFromConfig = getPagesFromConfig;
|
|
21284
21612
|
exports.getPagesFromProjectInfo = getPagesFromProjectInfo;
|
|
21285
21613
|
exports.getProjectValue = getProjectValue;
|
|
@@ -21290,6 +21618,7 @@ exports.getResourceUrl = getResourceUrl;
|
|
|
21290
21618
|
exports.getRootElementId = getRootElementId;
|
|
21291
21619
|
exports.getSelectedFilterValue = getSelectedFilterValue;
|
|
21292
21620
|
exports.getShownItemsLimit = getShownItemsLimit;
|
|
21621
|
+
exports.getSingleToken = getSingleToken;
|
|
21293
21622
|
exports.getSlideshowImages = getSlideshowImages;
|
|
21294
21623
|
exports.getStyleAttributes = getStyleAttributes;
|
|
21295
21624
|
exports.getSvgUrl = getSvgUrl;
|
|
@@ -21305,6 +21634,7 @@ exports.hasAxisConfig = hasAxisConfig;
|
|
|
21305
21634
|
exports.hasContainerBgImage = hasContainerBgImage;
|
|
21306
21635
|
exports.hexToRgba = hexToRgba;
|
|
21307
21636
|
exports.isCrossOriginUrl = isCrossOriginUrl;
|
|
21637
|
+
exports.isDashboardReference = isDashboardReference;
|
|
21308
21638
|
exports.isEmptyElementValue = isEmptyElementValue;
|
|
21309
21639
|
exports.isEmptyValue = isEmptyValue;
|
|
21310
21640
|
exports.isFeaturesFilterValue = isFeaturesFilterValue;
|
|
@@ -21323,6 +21653,7 @@ exports.isRootOwningContainer = isRootOwningContainer;
|
|
|
21323
21653
|
exports.isTreeFilterValue = isTreeFilterValue;
|
|
21324
21654
|
exports.isValueAxis = isValueAxis;
|
|
21325
21655
|
exports.isVisibleContainer = isVisibleContainer;
|
|
21656
|
+
exports.joinConfigSegments = joinConfigSegments;
|
|
21326
21657
|
exports.mapNodeById = mapNodeById;
|
|
21327
21658
|
exports.mergeAttributeConfigurations = mergeAttributeConfigurations;
|
|
21328
21659
|
exports.metersPerPixel = metersPerPixel;
|
|
@@ -21334,13 +21665,20 @@ exports.parseIconNamesFromClientStyle = parseIconNamesFromClientStyle;
|
|
|
21334
21665
|
exports.pieChartTooltipFromAttributes = pieChartTooltipFromAttributes;
|
|
21335
21666
|
exports.pieChartTooltipFromRelatedFeatures = pieChartTooltipFromRelatedFeatures;
|
|
21336
21667
|
exports.pointOptions = pointOptions;
|
|
21668
|
+
exports.referencesFilters = referencesFilters;
|
|
21337
21669
|
exports.removeDataSource = removeDataSource;
|
|
21338
21670
|
exports.removeTracks = removeTracks;
|
|
21339
21671
|
exports.replaceNodesByIds = replaceNodesByIds;
|
|
21340
21672
|
exports.resolveAxis = resolveAxis;
|
|
21673
|
+
exports.resolveConfigString = resolveConfigString;
|
|
21674
|
+
exports.resolveConfigToken = resolveConfigToken;
|
|
21675
|
+
exports.resolveElementStrings = resolveElementStrings;
|
|
21676
|
+
exports.resolveFilterDefaults = resolveFilterDefaults;
|
|
21677
|
+
exports.resolveQueryParameter = resolveQueryParameter;
|
|
21341
21678
|
exports.rgbToHex = rgbToHex;
|
|
21342
21679
|
exports.roundFr = roundFr;
|
|
21343
21680
|
exports.roundTotalSum = roundTotalSum;
|
|
21681
|
+
exports.selectUpdatingDataSources = selectUpdatingDataSources;
|
|
21344
21682
|
exports.setLayoutChildren = setLayoutChildren;
|
|
21345
21683
|
exports.sizeCssMixin = sizeCssMixin;
|
|
21346
21684
|
exports.sliceShownOtherItems = sliceShownOtherItems;
|
|
@@ -21351,6 +21689,7 @@ exports.toCssSize = toCssSize;
|
|
|
21351
21689
|
exports.toFrSize = toFrSize;
|
|
21352
21690
|
exports.toPxNumber = toPxNumber;
|
|
21353
21691
|
exports.toRenderableValue = toRenderableValue;
|
|
21692
|
+
exports.tokenizeConfigString = tokenizeConfigString;
|
|
21354
21693
|
exports.tooltipNameFromAttributes = tooltipNameFromAttributes;
|
|
21355
21694
|
exports.tooltipValueFromAttributes = tooltipValueFromAttributes;
|
|
21356
21695
|
exports.tooltipValueFromRelatedFeatures = tooltipValueFromRelatedFeatures;
|
|
@@ -21371,10 +21710,12 @@ exports.useChartAxisTitles = useChartAxisTitles;
|
|
|
21371
21710
|
exports.useChartChange = useChartChange;
|
|
21372
21711
|
exports.useChartData = useChartData;
|
|
21373
21712
|
exports.useConfigDataSources = useConfigDataSources;
|
|
21713
|
+
exports.useConfigStringSources = useConfigStringSources;
|
|
21374
21714
|
exports.useContainerAttributes = useContainerAttributes;
|
|
21375
21715
|
exports.useContainerRoot = useContainerRoot;
|
|
21376
21716
|
exports.useCurrentPageLayers = useCurrentPageLayers;
|
|
21377
21717
|
exports.useCustomFeatureSelect = useCustomFeatureSelect;
|
|
21718
|
+
exports.useDashboardFilterStore = useDashboardFilterStore;
|
|
21378
21719
|
exports.useDashboardHeader = useDashboardHeader;
|
|
21379
21720
|
exports.useDataSourceLoading = useDataSourceLoading;
|
|
21380
21721
|
exports.useDataSources = useDataSources;
|