@evergis/react 4.0.129 → 4.0.131
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 +1 -1
- package/dist/components/Dashboard/components/AddButton/styled.d.ts +15 -0
- package/dist/components/Dashboard/components/index.d.ts +1 -0
- package/dist/components/Dashboard/constants.d.ts +12 -0
- package/dist/components/Dashboard/containers/AttachmentContainer/styled.d.ts +0 -1
- package/dist/components/Dashboard/containers/StructuredDataContainer/constants.d.ts +2 -0
- package/dist/components/Dashboard/containers/StructuredDataContainer/hooks/useStructuredData.d.ts +4 -0
- package/dist/components/Dashboard/containers/StructuredDataContainer/styled.d.ts +3 -0
- package/dist/components/Dashboard/containers/StructuredDataContainer/utils/filterValue.d.ts +3 -2
- package/dist/components/Dashboard/elements/ElementTable/components/TableCell.d.ts +1 -1
- package/dist/components/Dashboard/elements/ElementTable/constants.d.ts +5 -0
- package/dist/components/Dashboard/elements/ElementTable/hooks/useCellEditing.d.ts +25 -9
- package/dist/components/Dashboard/elements/ElementTable/hooks/useHeadOverlap.d.ts +15 -0
- package/dist/components/Dashboard/elements/ElementTable/hooks/useTableView.d.ts +10 -1
- package/dist/components/Dashboard/elements/ElementTable/styled.d.ts +51 -4
- package/dist/components/Dashboard/elements/ElementTable/types.d.ts +5 -0
- package/dist/components/Dashboard/elements/ElementTable/utils/tableBox.d.ts +20 -0
- package/dist/components/Dashboard/hooks/index.d.ts +1 -0
- package/dist/components/Dashboard/hooks/useDataSourceLoading.d.ts +2 -0
- package/dist/components/Dashboard/hooks/useGlobalContext.d.ts +2 -0
- package/dist/components/Dashboard/types.d.ts +8 -6
- package/dist/components/Dashboard/utils/applyQueryFilters.d.ts +3 -1
- package/dist/components/Dashboard/utils/formatDataSourceCondition.d.ts +5 -1
- package/dist/components/Dashboard/utils/getMapViewDataSources.d.ts +7 -0
- package/dist/components/Dashboard/utils/index.d.ts +2 -0
- package/dist/components/Dashboard/utils/toConditionsArray.d.ts +2 -0
- package/dist/contexts/GlobalContext/types.d.ts +4 -0
- package/dist/index.js +445 -127
- package/dist/index.js.map +1 -1
- package/dist/react.esm.js +439 -129
- package/dist/react.esm.js.map +1 -1
- package/package.json +3 -3
- package/dist/components/Dashboard/containers/StructuredDataContainer/useStructuredData.d.ts +0 -28
- package/dist/components/Dashboard/containers/StructuredDataContainer/useStructuredDataDraft.d.ts +0 -25
- package/dist/components/Dashboard/containers/StructuredDataContainer/useStructuredDataSave.d.ts +0 -17
- package/dist/components/Dashboard/containers/StructuredDataContainer/useStructuredDataSchema.d.ts +0 -11
- package/dist/components/Dashboard/elements/ElementTable/useCellEditing.d.ts +0 -15
- package/dist/components/Dashboard/elements/ElementTable/useTableView.d.ts +0 -16
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var styled = require('styled-components');
|
|
4
4
|
var uilibGl = require('@evergis/uilib-gl');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
6
|
var React = require('react');
|
|
6
|
-
var styled = require('styled-components');
|
|
7
7
|
var charts = require('@evergis/charts');
|
|
8
8
|
var api = require('@evergis/api');
|
|
9
9
|
var lodash = require('lodash');
|
|
@@ -30,6 +30,45 @@ var xterm = require('@xterm/xterm');
|
|
|
30
30
|
var addonFit = require('@xterm/addon-fit');
|
|
31
31
|
require('@xterm/xterm/css/xterm.css');
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Кнопка «добавить»: серая скруглённая, со значком и подписью. Общая на весь дашборд — вложения
|
|
35
|
+
* и таблица структурированных данных добавляют одинаковой кнопкой, разница только в значке.
|
|
36
|
+
*
|
|
37
|
+
* Размеры сняты с макета (все — в половину его пикселей, макет снят на удвоенной плотности):
|
|
38
|
+
* высота 24, поля 10, просвет между значком и подписью 6, значок 14, подпись 14.
|
|
39
|
+
*
|
|
40
|
+
* Скругление и ширина по содержимому — от самого `IconButton`: он скруглён по умолчанию, а с
|
|
41
|
+
* подписью в детях перестаёт держать фиксированную ширину. Значок ужат до 0.875rem: свой 1rem
|
|
42
|
+
* он заполняет краями, и на макете кружок мельче подписи, а не вровень с ней.
|
|
43
|
+
*
|
|
44
|
+
* Подпись темнее значка: `IconButton` красит и то и другое цветом иконки, но в макете подпись —
|
|
45
|
+
* обычный текст (`textPrimary`), а приглушён только значок.
|
|
46
|
+
*/
|
|
47
|
+
const AddButtonRow = styled(uilibGl.IconButton).withConfig({ displayName: "AddButtonRow", componentId: "sc-3ytj1o" }) `
|
|
48
|
+
padding: 0 0.625rem;
|
|
49
|
+
height: 1.5rem;
|
|
50
|
+
background-color: ${({ theme }) => theme.palette.elementDark};
|
|
51
|
+
font-size: 0.875rem;
|
|
52
|
+
|
|
53
|
+
&:hover {
|
|
54
|
+
background-color: ${({ theme }) => theme.palette.elementDeep};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
${uilibGl.Icon} {
|
|
58
|
+
width: 0.875rem;
|
|
59
|
+
height: 0.875rem;
|
|
60
|
+
|
|
61
|
+
&:after {
|
|
62
|
+
font-size: 0.875rem;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
${uilibGl.IconButtonInnerChild} {
|
|
67
|
+
margin-left: 0.375rem;
|
|
68
|
+
color: ${({ theme }) => theme.palette.textPrimary};
|
|
69
|
+
}
|
|
70
|
+
`;
|
|
71
|
+
|
|
33
72
|
const AddFeatureButton = ({ title, icon = "feature_add" /* , layerName, geometryType*/ }) => {
|
|
34
73
|
// const [, handleAddFeature] = useFeatureCreator(layerName, geometryType);
|
|
35
74
|
const handleAddFeature = () => { };
|
|
@@ -3502,6 +3541,18 @@ const DEFAULT_CHART_ANGLE = 4;
|
|
|
3502
3541
|
const DEFAULT_CHART_HEIGHT = 90;
|
|
3503
3542
|
const STACK_BAR_TOTAL_HEIGHT = 20;
|
|
3504
3543
|
const FILTER_PREFIX = "%";
|
|
3544
|
+
/** Имя системного геометрического фильтра — выделение, нарисованное пользователем на карте. */
|
|
3545
|
+
const GEOMETRY_FILTER_NAME = "geometry";
|
|
3546
|
+
/** Имя системного фильтра «экстент видимой области карты» (EWKT, `SRID=3857`). */
|
|
3547
|
+
const EXTENT_FILTER_NAME = "extent";
|
|
3548
|
+
/** Имя системного фильтра «уровень зума карты» (целое число). */
|
|
3549
|
+
const ZOOM_FILTER_NAME = "zoom";
|
|
3550
|
+
/**
|
|
3551
|
+
* Системные фильтры текущего вида карты. Их значения приходят не из `SelectedFilters`, а из
|
|
3552
|
+
* `GlobalContext`, поэтому имена зарезервированы: одноимённый пользовательский фильтр будет
|
|
3553
|
+
* перехвачен системной подстановкой — ровно как в случае `geometry`.
|
|
3554
|
+
*/
|
|
3555
|
+
const MAP_VIEW_FILTER_NAMES = [EXTENT_FILTER_NAME, ZOOM_FILTER_NAME];
|
|
3505
3556
|
const PROVIDER_PREFIX = "$";
|
|
3506
3557
|
exports.ProviderPrefix = void 0;
|
|
3507
3558
|
(function (ProviderPrefix) {
|
|
@@ -4182,7 +4233,7 @@ const getSelectedFilterValue = (filterName, selectedFilters, defaultValue) => {
|
|
|
4182
4233
|
: selectedFilters?.[filterName]?.value) ?? defaultValue);
|
|
4183
4234
|
};
|
|
4184
4235
|
|
|
4185
|
-
const applyQueryFilters = ({ parameters: configParameters, filters: configFilters, selectedFilters, geometry, attributes, layerInfo, dataSources, projectDataSources, }) => {
|
|
4236
|
+
const applyQueryFilters = ({ parameters: configParameters, filters: configFilters, selectedFilters, geometry, extent, zoomLevel, attributes, layerInfo, dataSources, projectDataSources, }) => {
|
|
4186
4237
|
if (!configParameters) {
|
|
4187
4238
|
return {};
|
|
4188
4239
|
}
|
|
@@ -4227,12 +4278,27 @@ const applyQueryFilters = ({ parameters: configParameters, filters: configFilter
|
|
|
4227
4278
|
const [filterName, filterProp] = filterFullName.includes(".") ? filterFullName.split(".") : [filterFullName, null];
|
|
4228
4279
|
const configFilter = getConfigFilter(filterName, configFilters);
|
|
4229
4280
|
const { defaultValue, relatedDataSource, attributeAlias } = configFilter || {};
|
|
4230
|
-
if (filterName ===
|
|
4281
|
+
if (filterName === GEOMETRY_FILTER_NAME && geometry && !geometry.includes("()") && geometry.endsWith(")")) {
|
|
4231
4282
|
return {
|
|
4232
4283
|
...result,
|
|
4233
4284
|
[key]: geometry,
|
|
4234
4285
|
};
|
|
4235
4286
|
}
|
|
4287
|
+
// Системные фильтры вида карты: значение приходит из `GlobalContext`, а не из `SelectedFilters`,
|
|
4288
|
+
// поэтому резолвятся до веток `.min` / `.max` / `.property` — как `geometry` выше.
|
|
4289
|
+
if (filterName === EXTENT_FILTER_NAME && extent) {
|
|
4290
|
+
return {
|
|
4291
|
+
...result,
|
|
4292
|
+
[key]: extent,
|
|
4293
|
+
};
|
|
4294
|
+
}
|
|
4295
|
+
// Единственная нестроковая системная подстановка: уровень зума уходит числом.
|
|
4296
|
+
if (filterName === ZOOM_FILTER_NAME && !lodash.isNil(zoomLevel)) {
|
|
4297
|
+
return {
|
|
4298
|
+
...result,
|
|
4299
|
+
[key]: zoomLevel,
|
|
4300
|
+
};
|
|
4301
|
+
}
|
|
4236
4302
|
if (configParameters[key].endsWith(".max")) {
|
|
4237
4303
|
return {
|
|
4238
4304
|
...result,
|
|
@@ -4483,7 +4549,7 @@ const applyFiltersToCondition = ({ condition, name, defaultValue, filters, isSin
|
|
|
4483
4549
|
}
|
|
4484
4550
|
return isSingle && isNumeric(result) ? Number(result) : result;
|
|
4485
4551
|
};
|
|
4486
|
-
const applyVarsToCondition = ({ section, configFilters, filters, attributes, layerParams, eqlParameters, geometry, isSetParams, }) => {
|
|
4552
|
+
const applyVarsToCondition = ({ section, configFilters, filters, attributes, layerParams, eqlParameters, geometry, extent, zoomLevel, isSetParams, }) => {
|
|
4487
4553
|
if (!section?.length)
|
|
4488
4554
|
return [];
|
|
4489
4555
|
const isSingle = typeof section === "string";
|
|
@@ -4492,6 +4558,17 @@ const applyVarsToCondition = ({ section, configFilters, filters, attributes, lay
|
|
|
4492
4558
|
if (geometry && !geometry.includes("()") && geometry.endsWith(")")) {
|
|
4493
4559
|
result[index] = result[index].replace(new RegExp("%geometry"), `'${geometry}'`);
|
|
4494
4560
|
}
|
|
4561
|
+
// Системные фильтры вида карты подставляются ДО цикла по `configFilters`: одноимённый
|
|
4562
|
+
// пользовательский фильтр не должен перехватывать `%extent` / `%zoom`. Lookahead `(?![\w.])`
|
|
4563
|
+
// оставляет фильтрам составные формы (`%zoomLevel`, `%extent_id`, `%zoom.min`) — та же
|
|
4564
|
+
// граница, что уже разводит `%name` и `%name2.l1` в `getUpdatingDataSources`.
|
|
4565
|
+
if (extent) {
|
|
4566
|
+
result[index] = result[index].replace(new RegExp(`${FILTER_PREFIX}${EXTENT_FILTER_NAME}(?![\\w.])`, "g"), `'${extent}'`);
|
|
4567
|
+
}
|
|
4568
|
+
// Зум — число, кавычки не ставим.
|
|
4569
|
+
if (!lodash.isNil(zoomLevel)) {
|
|
4570
|
+
result[index] = result[index].replace(new RegExp(`${FILTER_PREFIX}${ZOOM_FILTER_NAME}(?![\\w.])`, "g"), String(zoomLevel));
|
|
4571
|
+
}
|
|
4495
4572
|
if (configFilters?.length) {
|
|
4496
4573
|
configFilters.forEach(filter => {
|
|
4497
4574
|
result[index] = applyFiltersToCondition({
|
|
@@ -4520,7 +4597,7 @@ const applyVarsToCondition = ({ section, configFilters, filters, attributes, lay
|
|
|
4520
4597
|
});
|
|
4521
4598
|
return isSingle ? result?.[0] : result;
|
|
4522
4599
|
};
|
|
4523
|
-
const formatSingleCondition = (condition, { configFilters, filters, attributes, eqlParameters, layerParams, geometry }) => {
|
|
4600
|
+
const formatSingleCondition = (condition, { configFilters, filters, attributes, eqlParameters, layerParams, geometry, extent, zoomLevel, }) => {
|
|
4524
4601
|
const setParams = condition.match(new RegExp("\\$\\([^)]+\\)", "g"));
|
|
4525
4602
|
const setParamsSection = applyVarsToCondition({
|
|
4526
4603
|
section: setParams,
|
|
@@ -4530,6 +4607,8 @@ const formatSingleCondition = (condition, { configFilters, filters, attributes,
|
|
|
4530
4607
|
eqlParameters,
|
|
4531
4608
|
layerParams,
|
|
4532
4609
|
geometry,
|
|
4610
|
+
extent,
|
|
4611
|
+
zoomLevel,
|
|
4533
4612
|
isSetParams: true,
|
|
4534
4613
|
});
|
|
4535
4614
|
const splitter = " AND ";
|
|
@@ -4542,6 +4621,8 @@ const formatSingleCondition = (condition, { configFilters, filters, attributes,
|
|
|
4542
4621
|
eqlParameters,
|
|
4543
4622
|
layerParams,
|
|
4544
4623
|
geometry,
|
|
4624
|
+
extent,
|
|
4625
|
+
zoomLevel,
|
|
4545
4626
|
});
|
|
4546
4627
|
return setParamsSection?.length && conditionSection.length
|
|
4547
4628
|
? [setParamsSection.join(""), conditionSection.join(splitter)].join(" ")
|
|
@@ -6319,7 +6400,7 @@ const useAttachmentItems = ({ type, elementConfig, valueOverride, }) => {
|
|
|
6319
6400
|
};
|
|
6320
6401
|
|
|
6321
6402
|
const useGlobalContext = () => {
|
|
6322
|
-
const { t, language, themeName, api, ewktGeometry, notification } = React.useContext(GlobalContext) || {};
|
|
6403
|
+
const { t, language, themeName, api, ewktGeometry, ewktExtent, zoomLevel, notification } = React.useContext(GlobalContext) || {};
|
|
6323
6404
|
const translate = React.useCallback((value, options) => {
|
|
6324
6405
|
if (t)
|
|
6325
6406
|
return t(value, options);
|
|
@@ -6331,8 +6412,10 @@ const useGlobalContext = () => {
|
|
|
6331
6412
|
themeName,
|
|
6332
6413
|
api,
|
|
6333
6414
|
ewktGeometry,
|
|
6415
|
+
ewktExtent,
|
|
6416
|
+
zoomLevel,
|
|
6334
6417
|
notification,
|
|
6335
|
-
}), [language, translate, api, ewktGeometry, themeName, notification]);
|
|
6418
|
+
}), [language, translate, api, ewktGeometry, ewktExtent, zoomLevel, themeName, notification]);
|
|
6336
6419
|
};
|
|
6337
6420
|
|
|
6338
6421
|
const GRID_TILE_SIZE = "4.5rem";
|
|
@@ -7138,10 +7221,17 @@ const useDashboardHeader = () => {
|
|
|
7138
7221
|
};
|
|
7139
7222
|
};
|
|
7140
7223
|
|
|
7224
|
+
// Полноэкранная заглушка допустима только пока не пришёл ни один источник: дальше страницу
|
|
7225
|
+
// и модалку наполняют сами контейнеры — у каждого свой ContainerLoading / ChartLoading.
|
|
7226
|
+
const useDataSourceLoading = (type) => {
|
|
7227
|
+
const { dataSources, isLoading } = useWidgetContext(type);
|
|
7228
|
+
const { currentPage } = useWidgetPage(type);
|
|
7229
|
+
return React.useMemo(() => !!currentPage?.dataSources?.length && !dataSources?.length && !!isLoading, [currentPage?.dataSources?.length, dataSources?.length, isLoading]);
|
|
7230
|
+
};
|
|
7231
|
+
|
|
7141
7232
|
/* eslint-disable max-lines */
|
|
7142
|
-
const toConditionsArray = (value) => Array.isArray(value) ? value : value ? [value] : [];
|
|
7143
7233
|
const useDataSources = ({ type: widgetType, config, attributes, filters, layerParams, eqlParameters, }) => {
|
|
7144
|
-
const { ewktGeometry, api } = useGlobalContext();
|
|
7234
|
+
const { ewktGeometry, ewktExtent, zoomLevel, api } = useGlobalContext();
|
|
7145
7235
|
const { dataSources, layerInfo } = useWidgetContext(widgetType);
|
|
7146
7236
|
const { dataSources: projectDataSources } = useWidgetContext(exports.WidgetType.Dashboard);
|
|
7147
7237
|
const { filters: configFilters, dataSources: configDataSources } = config || {};
|
|
@@ -7169,6 +7259,8 @@ const useDataSources = ({ type: widgetType, config, attributes, filters, layerPa
|
|
|
7169
7259
|
attributes,
|
|
7170
7260
|
filters: configFilters,
|
|
7171
7261
|
geometry: ewktGeometry,
|
|
7262
|
+
extent: ewktExtent,
|
|
7263
|
+
zoomLevel,
|
|
7172
7264
|
layerInfo,
|
|
7173
7265
|
dataSources,
|
|
7174
7266
|
projectDataSources,
|
|
@@ -7243,6 +7335,8 @@ const useDataSources = ({ type: widgetType, config, attributes, filters, layerPa
|
|
|
7243
7335
|
configFilters,
|
|
7244
7336
|
filters: selectedFilters,
|
|
7245
7337
|
geometry: ewktGeometry,
|
|
7338
|
+
extent: ewktExtent,
|
|
7339
|
+
zoomLevel,
|
|
7246
7340
|
attributes,
|
|
7247
7341
|
layerParams,
|
|
7248
7342
|
eqlParameters,
|
|
@@ -7264,6 +7358,8 @@ const useDataSources = ({ type: widgetType, config, attributes, filters, layerPa
|
|
|
7264
7358
|
dataSources,
|
|
7265
7359
|
configFilters,
|
|
7266
7360
|
ewktGeometry,
|
|
7361
|
+
ewktExtent,
|
|
7362
|
+
zoomLevel,
|
|
7267
7363
|
api,
|
|
7268
7364
|
attributes,
|
|
7269
7365
|
layerParams,
|
|
@@ -8735,19 +8831,10 @@ const ShowMoreButton$1 = styled.div.withConfig({ displayName: "ShowMoreButton",
|
|
|
8735
8831
|
const AddButtonContainer = styled.div.withConfig({ displayName: "AddButtonContainer", componentId: "sc-1yphz1r" }) `
|
|
8736
8832
|
margin-top: 0.75rem;
|
|
8737
8833
|
`;
|
|
8738
|
-
const
|
|
8739
|
-
padding: 0 0.75rem;
|
|
8740
|
-
height: 1.5rem;
|
|
8741
|
-
background-color: ${({ theme }) => theme.palette.elementDark};
|
|
8742
|
-
|
|
8743
|
-
&:hover {
|
|
8744
|
-
background-color: ${({ theme }) => theme.palette.elementDeep};
|
|
8745
|
-
}
|
|
8746
|
-
`;
|
|
8747
|
-
const HiddenFileInput = styled.input.withConfig({ displayName: "HiddenFileInput", componentId: "sc-kgi0m7" }) `
|
|
8834
|
+
const HiddenFileInput = styled.input.withConfig({ displayName: "HiddenFileInput", componentId: "sc-3apzla" }) `
|
|
8748
8835
|
display: none;
|
|
8749
8836
|
`;
|
|
8750
|
-
const LinkDialogContent = styled.div.withConfig({ displayName: "LinkDialogContent", componentId: "sc-
|
|
8837
|
+
const LinkDialogContent = styled.div.withConfig({ displayName: "LinkDialogContent", componentId: "sc-1kihzpg" }) `
|
|
8751
8838
|
display: flex;
|
|
8752
8839
|
flex-direction: column;
|
|
8753
8840
|
width: 100%;
|
|
@@ -10938,6 +11025,9 @@ const StructuredDataActions = styled(uilibGl.Flex).withConfig({ displayName: "St
|
|
|
10938
11025
|
/**
|
|
10939
11026
|
* Область представления. Прокручивается внутри себя, чтобы длинная таблица не растягивала
|
|
10940
11027
|
* контейнер за пределы отведённой ему ячейки; `min-height: 0` разрешает flex-ребёнку сжиматься.
|
|
11028
|
+
*
|
|
11029
|
+
* Собственный бокс таблицы (`options.width`/`height` представления) живёт внутри и на эту
|
|
11030
|
+
* прокрутку не влияет: он ограничивает саму таблицу, а не отведённое контейнером место.
|
|
10941
11031
|
*/
|
|
10942
11032
|
const StructuredDataView = styled(uilibGl.Flex).withConfig({ displayName: "StructuredDataView", componentId: "sc-1ws3t0d" }) `
|
|
10943
11033
|
flex-direction: column;
|
|
@@ -10954,6 +11044,33 @@ const StructuredDataView = styled(uilibGl.Flex).withConfig({ displayName: "Struc
|
|
|
10954
11044
|
*/
|
|
10955
11045
|
const StructuredDataContext = React.createContext(null);
|
|
10956
11046
|
|
|
11047
|
+
/** Слот представления: единственный ребёнок контейнера, рисующий данные (элемент `table`). */
|
|
11048
|
+
const STRUCTURED_DATA_VIEW_SLOT = "data";
|
|
11049
|
+
/**
|
|
11050
|
+
* Типы атрибутов с редактором ячейки. Атрибут любого другого типа показывается только на чтение,
|
|
11051
|
+
* даже если помечен `isEditable: true` — редактора для него нет.
|
|
11052
|
+
*/
|
|
11053
|
+
const EDITABLE_ATTRIBUTE_TYPES = [
|
|
11054
|
+
api.AttributeType.String,
|
|
11055
|
+
api.AttributeType.Int32,
|
|
11056
|
+
api.AttributeType.Int64,
|
|
11057
|
+
api.AttributeType.Double,
|
|
11058
|
+
api.AttributeType.Boolean,
|
|
11059
|
+
api.AttributeType.DateTime,
|
|
11060
|
+
];
|
|
11061
|
+
/** Типы, для которых ячейка редактируется числовым инпутом. */
|
|
11062
|
+
const NUMBER_EDITOR_TYPES = [
|
|
11063
|
+
api.AttributeType.Int32,
|
|
11064
|
+
api.AttributeType.Int64,
|
|
11065
|
+
api.AttributeType.Double,
|
|
11066
|
+
];
|
|
11067
|
+
/** Типы, значение которых хранится целым числом — дробную часть в них не пускаем. */
|
|
11068
|
+
const INTEGER_ATTRIBUTE_TYPES = [api.AttributeType.Int32, api.AttributeType.Int64];
|
|
11069
|
+
/** Тип атрибута, когда его не задали ни конфиг, ни источник. */
|
|
11070
|
+
const DEFAULT_ATTRIBUTE_TYPE = api.AttributeType.String;
|
|
11071
|
+
/** Сколько миллисекунд висит нотификация об ошибке записи. */
|
|
11072
|
+
const SAVE_ERROR_DURATION = 5000;
|
|
11073
|
+
|
|
10957
11074
|
/**
|
|
10958
11075
|
* Оставляет от произвольного набора свойств только атрибуты схемы. Отсутствующие добавляет
|
|
10959
11076
|
* как `null`: строка всегда имеет одинаковый набор ключей, иначе представление и
|
|
@@ -10991,8 +11108,9 @@ const getEditableProperties = (row, schema) => schema
|
|
|
10991
11108
|
/**
|
|
10992
11109
|
* Строки черновика → значение фильтра `valueType: "features"`.
|
|
10993
11110
|
*
|
|
10994
|
-
* В `properties`
|
|
10995
|
-
*
|
|
11111
|
+
* В `properties` попадает ровно схема: значения по её атрибутам, включая незаполненные. Лишние
|
|
11112
|
+
* ключи черновика (пришли из источника, остались от прежней схемы) наружу не уходят, а объявленный
|
|
11113
|
+
* атрибут уходит всегда. Геометрия не поддерживается — `geometry: null`.
|
|
10996
11114
|
*/
|
|
10997
11115
|
const toFeaturesFilterValue = (rows, schema) => ({
|
|
10998
11116
|
type: "FeatureCollection",
|
|
@@ -11041,14 +11159,17 @@ const useStructuredDataDraft = ({ schema, dataSource, hasDataSource, filterValue
|
|
|
11041
11159
|
}, [applyBaseline, features, hasDataSource, schema]);
|
|
11042
11160
|
// Ручная структура гидратируется из фильтра один раз: дальше значение фильтра пишем мы сами,
|
|
11043
11161
|
// и повторная гидратация только пересоздавала бы ключи строк.
|
|
11162
|
+
//
|
|
11163
|
+
// Флаг взводится по факту гидратации, а не авансом: значение может приехать позже схемы
|
|
11164
|
+
// (восстановленный выбор пользователя — асинхронно, в отличие от `defaultValue` из конфига),
|
|
11165
|
+
// и окно не должно сгорать на пустом первом рендере. Грязный черновик значение не затирает.
|
|
11044
11166
|
React.useEffect(() => {
|
|
11045
|
-
|
|
11167
|
+
const canHydrate = !hasDataSource && !hydrated.current && !dirtyRef.current && !!schema.length;
|
|
11168
|
+
if (!canHydrate || !isFeaturesFilterValue(filterValue)) {
|
|
11046
11169
|
return;
|
|
11047
11170
|
}
|
|
11048
11171
|
hydrated.current = true;
|
|
11049
|
-
|
|
11050
|
-
applyBaseline(fromFeaturesFilterValue(filterValue, schema));
|
|
11051
|
-
}
|
|
11172
|
+
applyBaseline(fromFeaturesFilterValue(filterValue, schema));
|
|
11052
11173
|
}, [applyBaseline, filterValue, hasDataSource, schema]);
|
|
11053
11174
|
const changeCell = React.useCallback((key, attributeName, value) => {
|
|
11054
11175
|
setRows(current => current.map(row => row.key === key
|
|
@@ -11071,36 +11192,16 @@ const useStructuredDataDraft = ({ schema, dataSource, hasDataSource, filterValue
|
|
|
11071
11192
|
}, []));
|
|
11072
11193
|
}, []);
|
|
11073
11194
|
const reset = React.useCallback(() => setRows(baseline.current), []);
|
|
11074
|
-
const commitSaved = React.useCallback((next) =>
|
|
11195
|
+
const commitSaved = React.useCallback((next) => {
|
|
11196
|
+
// Сохранённое состояние — наше: значение фильтра теперь пишем мы, и вернувшееся из него
|
|
11197
|
+
// обновление не должно пересоздавать ключи уже показанных строк.
|
|
11198
|
+
hydrated.current = true;
|
|
11199
|
+
applyBaseline(next);
|
|
11200
|
+
}, [applyBaseline]);
|
|
11075
11201
|
const visibleRows = React.useMemo(() => rows.filter(({ state }) => state !== "deleted"), [rows]);
|
|
11076
11202
|
return { rows, visibleRows, dirty, changeCell, addRow, deleteRow, reset, commitSaved };
|
|
11077
11203
|
};
|
|
11078
11204
|
|
|
11079
|
-
/**
|
|
11080
|
-
* Типы атрибутов с редактором ячейки. Атрибут любого другого типа показывается только на чтение,
|
|
11081
|
-
* даже если помечен `isEditable: true` — редактора для него нет.
|
|
11082
|
-
*/
|
|
11083
|
-
const EDITABLE_ATTRIBUTE_TYPES = [
|
|
11084
|
-
api.AttributeType.String,
|
|
11085
|
-
api.AttributeType.Int32,
|
|
11086
|
-
api.AttributeType.Int64,
|
|
11087
|
-
api.AttributeType.Double,
|
|
11088
|
-
api.AttributeType.Boolean,
|
|
11089
|
-
api.AttributeType.DateTime,
|
|
11090
|
-
];
|
|
11091
|
-
/** Типы, для которых ячейка редактируется числовым инпутом. */
|
|
11092
|
-
const NUMBER_EDITOR_TYPES = [
|
|
11093
|
-
api.AttributeType.Int32,
|
|
11094
|
-
api.AttributeType.Int64,
|
|
11095
|
-
api.AttributeType.Double,
|
|
11096
|
-
];
|
|
11097
|
-
/** Типы, значение которых хранится целым числом — дробную часть в них не пускаем. */
|
|
11098
|
-
const INTEGER_ATTRIBUTE_TYPES = [api.AttributeType.Int32, api.AttributeType.Int64];
|
|
11099
|
-
/** Тип атрибута, когда его не задали ни конфиг, ни источник. */
|
|
11100
|
-
const DEFAULT_ATTRIBUTE_TYPE = api.AttributeType.String;
|
|
11101
|
-
/** Сколько миллисекунд висит нотификация об ошибке записи. */
|
|
11102
|
-
const SAVE_ERROR_DURATION = 5000;
|
|
11103
|
-
|
|
11104
11205
|
/**
|
|
11105
11206
|
* Применяет черновик к слою источника: удаление → обновление → создание.
|
|
11106
11207
|
*
|
|
@@ -11267,13 +11368,19 @@ const useStructuredDataSchema = (type, elementConfig) => {
|
|
|
11267
11368
|
* Вид источника на права правки не влияет — от него зависит только адресат «Сохранить». Слой
|
|
11268
11369
|
* принимает запись через features-API, источник без `layerName` (EQL, python-скрипт, внешний
|
|
11269
11370
|
* url) не принимает ничего, и правка такой таблицы уходит только в фильтр.
|
|
11371
|
+
*
|
|
11372
|
+
* Значение фильтра читается так же, как его читают остальные потребители дашборда: выбор
|
|
11373
|
+
* пользователя, а пока его нет — `defaultValue` из конфига фильтра. Общего стейта, залитого
|
|
11374
|
+
* дефолтами, в дашборде нет, поэтому фолбэк делает каждый потребитель сам.
|
|
11270
11375
|
*/
|
|
11271
11376
|
const useStructuredData = (type, elementConfig) => {
|
|
11272
11377
|
const { filters } = useWidgetContext(type);
|
|
11378
|
+
const { currentPage } = useWidgetPage(type);
|
|
11273
11379
|
const { relatedDataSource, filterName, editMode } = elementConfig?.options || {};
|
|
11274
11380
|
const { schema, dataSource, layerName } = useStructuredDataSchema(type, elementConfig);
|
|
11275
11381
|
const hasDataSource = !!relatedDataSource;
|
|
11276
|
-
const
|
|
11382
|
+
const configFilter = React.useMemo(() => getConfigFilter(filterName, currentPage?.filters), [currentPage?.filters, filterName]);
|
|
11383
|
+
const filterValue = React.useMemo(() => (filterName ? filters?.[filterName]?.value ?? configFilter?.defaultValue : undefined), [configFilter?.defaultValue, filterName, filters]);
|
|
11277
11384
|
const { rows, visibleRows, dirty, changeCell, addRow, deleteRow, reset, commitSaved } = useStructuredDataDraft({
|
|
11278
11385
|
schema,
|
|
11279
11386
|
dataSource,
|
|
@@ -11346,7 +11453,7 @@ const StructuredDataContainer = React.memo(({ type, elementConfig, isVisible, re
|
|
|
11346
11453
|
if (hasError) {
|
|
11347
11454
|
return jsxRuntime.jsx(DataSourceError, { name: elementConfig?.templateName });
|
|
11348
11455
|
}
|
|
11349
|
-
return (jsxRuntime.jsxs(ContainerRoot, { ...root, children: [jsxRuntime.jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsxRuntime.jsxs(Container, { ...body, isColumn: true, children: [jsxRuntime.jsx(StructuredDataView, { children: jsxRuntime.jsx(StructuredDataContext.Provider, { value: contextValue, children: renderElement({ id:
|
|
11456
|
+
return (jsxRuntime.jsxs(ContainerRoot, { ...root, children: [jsxRuntime.jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsxRuntime.jsxs(Container, { ...body, isColumn: true, children: [jsxRuntime.jsx(StructuredDataView, { children: jsxRuntime.jsx(StructuredDataContext.Provider, { value: contextValue, children: renderElement({ id: STRUCTURED_DATA_VIEW_SLOT }) }) }), canEdit && (jsxRuntime.jsxs(StructuredDataToolbar, { children: [jsxRuntime.jsx(StructuredDataActions, { children: jsxRuntime.jsx(AddButtonRow, { kind: "feature_add", tabIndex: 0, disabled: saving, onClick: onAddRow, children: t("actions.add", { ns: "common", defaultValue: "Добавить" }) }) }), dirty && (jsxRuntime.jsxs(StructuredDataActions, { children: [jsxRuntime.jsx(uilibGl.IconButton, { error: true, kind: "close", tabIndex: 0, disabled: saving, title: t("actions.cancel", { ns: "common", defaultValue: "Отменить" }), onClick: onCancel }), jsxRuntime.jsx(uilibGl.IconButton, { primary: true, kind: "success", tabIndex: 0, disabled: saving, title: t("actions.save", { ns: "common", defaultValue: "Сохранить" }), onClick: onSave })] }))] }))] })), jsxRuntime.jsx(DeleteRowDialog, { isOpen: isDeleteConfirmOpen, isSourceRow: isDeletingSourceRow, onConfirm: onDeleteConfirm, onCancel: onDeleteCancel })] }));
|
|
11350
11457
|
});
|
|
11351
11458
|
|
|
11352
11459
|
const TabAnchor = styled.div.withConfig({ displayName: "TabAnchor", componentId: "sc-emqf31" }) `
|
|
@@ -14297,28 +14404,86 @@ const ElementSvg = React.memo(({ type, elementConfig, ...rest }) => {
|
|
|
14297
14404
|
return (jsxRuntime.jsx(SvgImage, { url: getSvgUrl({ elementConfig, layerInfo, attributes }), width: width, height: height, fontColor: fontColor }));
|
|
14298
14405
|
});
|
|
14299
14406
|
|
|
14300
|
-
|
|
14301
|
-
|
|
14302
|
-
|
|
14407
|
+
/**
|
|
14408
|
+
* Насколько тело таблицы заехало под липкую шапку. Пишется на таблицу при прокрутке, читается
|
|
14409
|
+
* стилем тела: ровно на эту величину тело и отсекается.
|
|
14410
|
+
*/
|
|
14411
|
+
const HEAD_OVERLAP_VARIABLE = "--table-head-overlap";
|
|
14412
|
+
/**
|
|
14413
|
+
* Признак времени в шаблоне `stringFormat.format`.
|
|
14414
|
+
*
|
|
14415
|
+
* Варианты времени в конфигурации атрибута — `hh:mm`, `hh:mm tt`, `hh:mm:ss`, `hh:mm:ss tt`
|
|
14416
|
+
* (см. `timeOptions` в `utils/format`), поэтому достаточно найти часы с минутами.
|
|
14417
|
+
*/
|
|
14418
|
+
const TIME_FORMAT = /hh:mm/;
|
|
14419
|
+
/**
|
|
14420
|
+
* Контейнер порталов `@evergis/uilib-gl` — в него попадают выпадающие слои контролов,
|
|
14421
|
+
* в том числе календарь `DatePicker`. Клик по ним DOM-деревом лежит вне ячейки, поэтому
|
|
14422
|
+
* закрытие редактора по клику снаружи обязано этот контейнер пропускать.
|
|
14423
|
+
*/
|
|
14424
|
+
const PORTAL_ROOT_SELECTOR = "#portal-root";
|
|
14425
|
+
|
|
14426
|
+
/**
|
|
14427
|
+
* Собственный бокс таблицы. Без размеров в `options` представления узел ничего не меняет:
|
|
14428
|
+
* таблица растёт под содержимое, а прокручивает лишнее область представления контейнера.
|
|
14429
|
+
*
|
|
14430
|
+
* С размерами бокс становится её скролл-контейнером — и липкая шапка липнет уже к нему.
|
|
14431
|
+
*/
|
|
14432
|
+
const TableBox = styled.div.withConfig({ displayName: "TableBox", componentId: "sc-19vtvuq" }) `
|
|
14433
|
+
min-width: 0;
|
|
14434
|
+
|
|
14435
|
+
${sizeCssMixin};
|
|
14436
|
+
`;
|
|
14437
|
+
/**
|
|
14438
|
+
* Таблица. По умолчанию втискивается в ширину, которую ей дали: колонкам достаётся то, что есть,
|
|
14439
|
+
* а длинные значения режутся многоточием внутри ячейки.
|
|
14440
|
+
*
|
|
14441
|
+
* `$contentWidth` (ширина бокса задана в `options`) переводит её на ширину по содержимому:
|
|
14442
|
+
* колонки встают по своим значениям, предел ячейки снимается, и всё, что не влезло, уходит
|
|
14443
|
+
* в горизонтальную прокрутку бокса. Иначе вышла бы бессмыслица — бокс с прокруткой, а значения
|
|
14444
|
+
* всё равно с многоточием. `min-width` оставляет таблицу растянутой на весь бокс, когда
|
|
14445
|
+
* содержимого на его ширину не набирается.
|
|
14446
|
+
*
|
|
14447
|
+
* Снятый предел ширины ячейки едет вниз css-переменной, а не правилом по `TableCellWrapper`:
|
|
14448
|
+
* ячейка объявлена ниже по файлу, и селектор по ней отсюда попал бы в TDZ.
|
|
14449
|
+
*/
|
|
14450
|
+
const TableWrapper = styled.table.withConfig({ displayName: "TableWrapper", componentId: "sc-ih9qqq" }) `
|
|
14451
|
+
width: ${({ $contentWidth }) => ($contentWidth ? "max-content" : "100%")};
|
|
14452
|
+
min-width: 100%;
|
|
14453
|
+
/*
|
|
14454
|
+
* Рамки раздельные (со схлопнутым интервалом, поэтому выглядят как схлопнутые). Схлопнутые
|
|
14455
|
+
* рисует сама таблица, а не ячейки, и отсечка тела их не режет: линия строки, уехавшей под
|
|
14456
|
+
* шапку, продолжала бы рисоваться поверх заголовков.
|
|
14457
|
+
*/
|
|
14458
|
+
border-collapse: separate;
|
|
14459
|
+
border-spacing: 0;
|
|
14303
14460
|
font-size: 0.875rem;
|
|
14461
|
+
|
|
14462
|
+
${({ $contentWidth }) => !!$contentWidth &&
|
|
14463
|
+
styled.css `
|
|
14464
|
+
--table-cell-max-width: none;
|
|
14465
|
+
`};
|
|
14304
14466
|
`;
|
|
14305
14467
|
/**
|
|
14306
14468
|
* Шапка липнет к верху области прокрутки — при длинной таблице заголовки остаются видны.
|
|
14307
14469
|
*
|
|
14308
|
-
*
|
|
14309
|
-
*
|
|
14470
|
+
* Своего фона у неё нет и не должно быть: контейнер дашборда красится цветом из конфига, и любой
|
|
14471
|
+
* назначенный шапке цвет остался бы прямоугольником чужого цвета, как только фон контейнера
|
|
14472
|
+
* поменяли. Под шапкой виден фон контейнера, а строки под неё не заезжают — тело отсечено
|
|
14473
|
+
* (см. TableBody и useHeadOverlap).
|
|
14310
14474
|
*/
|
|
14311
|
-
const TableHead = styled.thead.withConfig({ displayName: "TableHead", componentId: "sc-
|
|
14475
|
+
const TableHead = styled.thead.withConfig({ displayName: "TableHead", componentId: "sc-m8fpi" }) `
|
|
14312
14476
|
position: sticky;
|
|
14313
14477
|
top: 0;
|
|
14314
14478
|
z-index: 1;
|
|
14315
14479
|
`;
|
|
14316
|
-
const TableHeadCell = styled.th.withConfig({ displayName: "TableHeadCell", componentId: "sc-
|
|
14480
|
+
const TableHeadCell = styled.th.withConfig({ displayName: "TableHeadCell", componentId: "sc-1m6xil2" }) `
|
|
14317
14481
|
padding: 0.375rem 0.5rem;
|
|
14318
14482
|
text-align: left;
|
|
14319
14483
|
font-weight: 600;
|
|
14320
14484
|
white-space: nowrap;
|
|
14321
14485
|
color: ${({ theme }) => theme.palette.textSecondary};
|
|
14486
|
+
/* Рамка принадлежит ячейке (рамки раздельные), поэтому едет вместе с липкой шапкой. */
|
|
14322
14487
|
border-bottom: 1px solid ${({ theme }) => theme.palette.elementDeep};
|
|
14323
14488
|
|
|
14324
14489
|
${({ $sortable }) => $sortable &&
|
|
@@ -14327,25 +14492,50 @@ const TableHeadCell = styled.th.withConfig({ displayName: "TableHeadCell", compo
|
|
|
14327
14492
|
user-select: none;
|
|
14328
14493
|
`}
|
|
14329
14494
|
`;
|
|
14330
|
-
const TableHeadContent = styled.span.withConfig({ displayName: "TableHeadContent", componentId: "sc-
|
|
14495
|
+
const TableHeadContent = styled.span.withConfig({ displayName: "TableHeadContent", componentId: "sc-8hzcop" }) `
|
|
14331
14496
|
display: inline-flex;
|
|
14332
14497
|
align-items: center;
|
|
14333
14498
|
gap: 0.25rem;
|
|
14334
14499
|
`;
|
|
14335
|
-
|
|
14336
|
-
|
|
14500
|
+
/**
|
|
14501
|
+
* Место под значок сортировки. Занято всегда, пока сортировка включена, а сам значок только
|
|
14502
|
+
* прячется: появляясь и исчезая, он менял бы ширину колонки — и таблицу дёргало бы на каждый
|
|
14503
|
+
* клик по заголовку, вместе с телом. Оба направления рисуются значком в 1rem, поэтому
|
|
14504
|
+
* переключение asc/des ширину тоже не трогает.
|
|
14505
|
+
*/
|
|
14506
|
+
const TableSortSlot = styled.span.withConfig({ displayName: "TableSortSlot", componentId: "sc-z378vf" }) `
|
|
14507
|
+
display: inline-flex;
|
|
14508
|
+
visibility: ${({ $active }) => ($active ? "visible" : "hidden")};
|
|
14337
14509
|
`;
|
|
14338
|
-
|
|
14510
|
+
/**
|
|
14511
|
+
* Тело таблицы. Отсекается ровно на то, на сколько его накрыла липкая шапка: строки уходят под
|
|
14512
|
+
* её нижнюю кромку и сквозь прозрачную шапку не проступают. Величину пишет {@link useHeadOverlap},
|
|
14513
|
+
* без неё отсечка нулевая и разметка не меняется.
|
|
14514
|
+
*/
|
|
14515
|
+
const TableBody = styled.tbody.withConfig({ displayName: "TableBody", componentId: "sc-1pxpl5v" }) `
|
|
14516
|
+
clip-path: inset(var(${HEAD_OVERLAP_VARIABLE}, 0px) 0 0 0);
|
|
14517
|
+
`;
|
|
14518
|
+
/**
|
|
14519
|
+
* Строка. Разделитель рисуют её ячейки: собственная рамка строки при раздельных рамках не
|
|
14520
|
+
* рисуется вовсе, а при схлопнутых её рисовала бы таблица — и отсечка тела её бы не резала.
|
|
14521
|
+
*/
|
|
14522
|
+
const TableRow = styled.tr.withConfig({ displayName: "TableRow", componentId: "sc-j62zvd" }) `
|
|
14523
|
+
td {
|
|
14524
|
+
border-bottom: 1px solid ${({ theme }) => theme.palette.elementDeep};
|
|
14525
|
+
}
|
|
14526
|
+
`;
|
|
14527
|
+
const TableCellWrapper = styled.td.withConfig({ displayName: "TableCellWrapper", componentId: "sc-ziu5rk" }) `
|
|
14339
14528
|
padding: 0.125rem 0.25rem;
|
|
14340
14529
|
vertical-align: middle;
|
|
14341
|
-
|
|
14530
|
+
/* Предел снимает заданная ширина: с колонками по содержимому обрезать значение незачем. */
|
|
14531
|
+
max-width: var(--table-cell-max-width, 20rem);
|
|
14342
14532
|
`;
|
|
14343
14533
|
/** Колонка действий: узкая, не растягивается содержимым. */
|
|
14344
|
-
const TableActionsCell = styled(TableCellWrapper).withConfig({ displayName: "TableActionsCell", componentId: "sc-
|
|
14534
|
+
const TableActionsCell = styled(TableCellWrapper).withConfig({ displayName: "TableActionsCell", componentId: "sc-18w13t3" }) `
|
|
14345
14535
|
width: 2rem;
|
|
14346
14536
|
text-align: right;
|
|
14347
14537
|
`;
|
|
14348
|
-
const CellText = styled.div.withConfig({ displayName: "CellText", componentId: "sc-
|
|
14538
|
+
const CellText = styled.div.withConfig({ displayName: "CellText", componentId: "sc-ok630s" }) `
|
|
14349
14539
|
padding: 0.375rem 0.25rem;
|
|
14350
14540
|
/* Та же высота строки, что у редактора — иначе строка подпрыгивала бы по вертикали. */
|
|
14351
14541
|
line-height: 1.25rem;
|
|
@@ -14355,9 +14545,12 @@ const CellText = styled.div.withConfig({ displayName: "CellText", componentId: "
|
|
|
14355
14545
|
`;
|
|
14356
14546
|
/**
|
|
14357
14547
|
* Редактируемая ячейка вне фокуса. Показывает значение по `stringFormat`, а редактор
|
|
14358
|
-
* подставляется
|
|
14548
|
+
* подставляется на фокус — иначе форматирование пришлось бы дублировать внутри инпута.
|
|
14549
|
+
*
|
|
14550
|
+
* Кнопка, а не просто текст, именно ради фокуса: она — таб-стоп ячейки, и Tab по таблице
|
|
14551
|
+
* открывает редактор с курсором внутри.
|
|
14359
14552
|
*/
|
|
14360
|
-
const CellButton = styled.button.withConfig({ displayName: "CellButton", componentId: "sc-
|
|
14553
|
+
const CellButton = styled.button.withConfig({ displayName: "CellButton", componentId: "sc-98k9cz" }) `
|
|
14361
14554
|
width: 100%;
|
|
14362
14555
|
padding: 0.375rem 0.25rem;
|
|
14363
14556
|
line-height: 1.25rem;
|
|
@@ -14376,7 +14569,7 @@ const CellButton = styled.button.withConfig({ displayName: "CellButton", compone
|
|
|
14376
14569
|
border-color: ${({ theme }) => theme.palette.elementDeep};
|
|
14377
14570
|
}
|
|
14378
14571
|
`;
|
|
14379
|
-
const CellPlaceholder = styled.span.withConfig({ displayName: "CellPlaceholder", componentId: "sc-
|
|
14572
|
+
const CellPlaceholder = styled.span.withConfig({ displayName: "CellPlaceholder", componentId: "sc-7tff41" }) `
|
|
14380
14573
|
color: ${({ theme }) => theme.palette.textSecondary};
|
|
14381
14574
|
`;
|
|
14382
14575
|
/**
|
|
@@ -14385,11 +14578,11 @@ const CellPlaceholder = styled.span.withConfig({ displayName: "CellPlaceholder",
|
|
|
14385
14578
|
* Копия остаётся в потоке и держит ширину колонки ровно такой, какой она была до клика, —
|
|
14386
14579
|
* иначе таблица дёргалась бы на каждый вход в правку и выход из неё.
|
|
14387
14580
|
*/
|
|
14388
|
-
const CellEditor = styled.div.withConfig({ displayName: "CellEditor", componentId: "sc-
|
|
14581
|
+
const CellEditor = styled.div.withConfig({ displayName: "CellEditor", componentId: "sc-qa22du" }) `
|
|
14389
14582
|
position: relative;
|
|
14390
14583
|
min-height: 2rem;
|
|
14391
14584
|
`;
|
|
14392
|
-
const CellGhost = styled.span.withConfig({ displayName: "CellGhost", componentId: "sc-
|
|
14585
|
+
const CellGhost = styled.span.withConfig({ displayName: "CellGhost", componentId: "sc-1i1yvb2" }) `
|
|
14393
14586
|
display: block;
|
|
14394
14587
|
padding: 0.375rem 0.25rem;
|
|
14395
14588
|
/* Рамка повторяет CellButton — с ней высота ячейки в правке совпадает с высотой на чтении. */
|
|
@@ -14398,7 +14591,7 @@ const CellGhost = styled.span.withConfig({ displayName: "CellGhost", componentId
|
|
|
14398
14591
|
white-space: nowrap;
|
|
14399
14592
|
visibility: hidden;
|
|
14400
14593
|
`;
|
|
14401
|
-
const CellField = styled.div.withConfig({ displayName: "CellField", componentId: "sc-
|
|
14594
|
+
const CellField = styled.div.withConfig({ displayName: "CellField", componentId: "sc-1ww3875" }) `
|
|
14402
14595
|
position: absolute;
|
|
14403
14596
|
inset: 0;
|
|
14404
14597
|
display: flex;
|
|
@@ -14413,7 +14606,7 @@ const CellField = styled.div.withConfig({ displayName: "CellField", componentId:
|
|
|
14413
14606
|
min-width: 0;
|
|
14414
14607
|
}
|
|
14415
14608
|
`;
|
|
14416
|
-
const TableEmpty = styled.div.withConfig({ displayName: "TableEmpty", componentId: "sc-
|
|
14609
|
+
const TableEmpty = styled.div.withConfig({ displayName: "TableEmpty", componentId: "sc-1vamijg" }) `
|
|
14417
14610
|
padding: 0.75rem 0.25rem;
|
|
14418
14611
|
color: ${({ theme }) => theme.palette.textSecondary};
|
|
14419
14612
|
`;
|
|
@@ -14433,38 +14626,60 @@ const isSameCellValue = (next, current, type) => {
|
|
|
14433
14626
|
};
|
|
14434
14627
|
|
|
14435
14628
|
/**
|
|
14436
|
-
*
|
|
14629
|
+
* Правка одной ячейки.
|
|
14437
14630
|
*
|
|
14438
|
-
*
|
|
14439
|
-
*
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
*
|
|
14444
|
-
* в том числе календарь `DatePicker`. Клик по ним DOM-деревом лежит вне ячейки, поэтому
|
|
14445
|
-
* закрытие редактора по клику снаружи обязано этот контейнер пропускать.
|
|
14446
|
-
*/
|
|
14447
|
-
const PORTAL_ROOT_SELECTOR = "#portal-root";
|
|
14448
|
-
|
|
14449
|
-
/**
|
|
14450
|
-
* Режим правки одной ячейки: открывается кликом по значению, закрывается кликом снаружи.
|
|
14631
|
+
* Открывает её фокус, а не клик: по таблице ходят Tab-ом, и на каждой ячейке сразу нужно поле
|
|
14632
|
+
* с курсором — кнопка, требующая ещё и Enter, ломала бы весь проход.
|
|
14633
|
+
*
|
|
14634
|
+
* Поэтому состояний три. `focused` — ячейка под фокусом с закрытым редактором, состояние после
|
|
14635
|
+
* Enter/Escape: фокус возвращается на кнопку, но правку заново не открывает (иначе выйти из неё
|
|
14636
|
+
* было бы нечем), а Tab с неё идёт дальше по таблице.
|
|
14451
14637
|
*
|
|
14452
|
-
* Закрывать по
|
|
14453
|
-
* календаря — редактор исчезал бы раньше, чем календарь успевал открыться.
|
|
14454
|
-
* `
|
|
14455
|
-
*
|
|
14638
|
+
* Закрывать правку по любому уходу фокуса нельзя: DatePicker уводит его из поля уже на клике по
|
|
14639
|
+
* иконке календаря — редактор исчезал бы раньше, чем календарь успевал открыться. Клавиатурный
|
|
14640
|
+
* уход видно по `relatedTarget` (фокус ушёл на соседнюю ячейку), мышиный ловит `mousedown`
|
|
14641
|
+
* снаружи. Клики внутри портала (`#portal-root`) считаем «своими»: выпадающие слои контролов
|
|
14642
|
+
* uilib живут именно там, вне DOM-поддерева ячейки.
|
|
14456
14643
|
*/
|
|
14457
14644
|
const useCellEditing = () => {
|
|
14458
|
-
const [
|
|
14645
|
+
const [mode, setMode] = React.useState("idle");
|
|
14646
|
+
const buttonRef = React.useRef(null);
|
|
14459
14647
|
const editorRef = React.useRef(null);
|
|
14460
|
-
const
|
|
14461
|
-
//
|
|
14462
|
-
// из
|
|
14463
|
-
const
|
|
14464
|
-
|
|
14465
|
-
|
|
14648
|
+
const editing = mode === "editing";
|
|
14649
|
+
// Фокус на закрытой ячейке — это уже правка. Из `focused` не открываем: фокус туда только что
|
|
14650
|
+
// вернули из редактора, и Escape перестал бы работать.
|
|
14651
|
+
const onFocus = React.useCallback(() => setMode(current => (current === "idle" ? "editing" : current)), []);
|
|
14652
|
+
// Переход в правку убирает саму кнопку — её прощальный blur эту правку закрывать не должен.
|
|
14653
|
+
const onBlur = React.useCallback(() => setMode(current => (current === "editing" ? current : "idle")), []);
|
|
14654
|
+
// Клик открывает правку и тогда, когда фокус на ячейке уже стоит — то есть после Enter/Escape.
|
|
14655
|
+
const onClick = React.useCallback(() => setMode("editing"), []);
|
|
14656
|
+
// Enter и Escape закрывают редактор, оставляя фокус на ячейке: значение записано в черновик
|
|
14657
|
+
// на каждый ввод, отдельного «применить» нет.
|
|
14658
|
+
const onEditorKeyDown = React.useCallback((event) => {
|
|
14659
|
+
if (event.key !== "Enter" && event.key !== "Escape") {
|
|
14660
|
+
return;
|
|
14661
|
+
}
|
|
14662
|
+
// Гасим действие по умолчанию: у Enter это «нажать то, что в фокусе», а фокус браузер смотрит
|
|
14663
|
+
// уже после обработчиков — к тому моменту он вернулся на кнопку ячейки, и правка открылась бы
|
|
14664
|
+
// заново тем же нажатием.
|
|
14665
|
+
event.preventDefault();
|
|
14666
|
+
setMode("focused");
|
|
14667
|
+
}, []);
|
|
14668
|
+
const onEditorBlur = React.useCallback(({ relatedTarget }) => {
|
|
14669
|
+
const inside = editorRef.current?.contains(relatedTarget) || !!relatedTarget?.closest(PORTAL_ROOT_SELECTOR);
|
|
14670
|
+
// Фокус «в никуда» уходом не считаем: так ведут себя внутренности DatePicker.
|
|
14671
|
+
if (!relatedTarget || inside) {
|
|
14672
|
+
return;
|
|
14466
14673
|
}
|
|
14674
|
+
setMode("idle");
|
|
14467
14675
|
}, []);
|
|
14676
|
+
// Выход из редактора возвращает фокус ячейке: иначе он падал бы в body, и следующий Tab пошёл
|
|
14677
|
+
// бы с начала страницы.
|
|
14678
|
+
React.useEffect(() => {
|
|
14679
|
+
if (mode === "focused") {
|
|
14680
|
+
buttonRef.current?.focus();
|
|
14681
|
+
}
|
|
14682
|
+
}, [mode]);
|
|
14468
14683
|
React.useEffect(() => {
|
|
14469
14684
|
if (!editing) {
|
|
14470
14685
|
return undefined;
|
|
@@ -14474,24 +14689,26 @@ const useCellEditing = () => {
|
|
|
14474
14689
|
if (editorRef.current?.contains(node) || node?.closest?.(PORTAL_ROOT_SELECTOR)) {
|
|
14475
14690
|
return;
|
|
14476
14691
|
}
|
|
14477
|
-
|
|
14692
|
+
setMode("idle");
|
|
14478
14693
|
};
|
|
14479
14694
|
document.addEventListener("mousedown", onDocumentMouseDown);
|
|
14480
14695
|
return () => document.removeEventListener("mousedown", onDocumentMouseDown);
|
|
14481
14696
|
}, [editing]);
|
|
14482
|
-
|
|
14697
|
+
const buttonProps = React.useMemo(() => ({ ref: buttonRef, onFocus, onBlur, onClick }), [onBlur, onClick, onFocus]);
|
|
14698
|
+
const editorProps = React.useMemo(() => ({ ref: editorRef, onBlur: onEditorBlur, onKeyDown: onEditorKeyDown }), [onEditorBlur, onEditorKeyDown]);
|
|
14699
|
+
return { editing, buttonProps, editorProps };
|
|
14483
14700
|
};
|
|
14484
14701
|
|
|
14485
14702
|
/**
|
|
14486
14703
|
* Ячейка таблицы.
|
|
14487
14704
|
*
|
|
14488
|
-
* Вне фокуса значение всегда показано через `stringFormat
|
|
14705
|
+
* Вне фокуса значение всегда показано через `stringFormat`, редактор подставляется на фокус.
|
|
14489
14706
|
* Иначе форматирование (округление, разряды, единицы) пришлось бы дублировать внутри инпута,
|
|
14490
14707
|
* а вводить в отформатированное поле нельзя.
|
|
14491
14708
|
*/
|
|
14492
14709
|
const TableCell = React.memo(({ attribute, row, canEdit, onChange }) => {
|
|
14493
14710
|
const { t, language } = useGlobalContext();
|
|
14494
|
-
const { editing,
|
|
14711
|
+
const { editing, buttonProps, editorProps } = useCellEditing();
|
|
14495
14712
|
const { attributeName, type, isEditable, stringFormat } = attribute;
|
|
14496
14713
|
const value = row.properties[attributeName];
|
|
14497
14714
|
const handleChange = React.useCallback((next) => {
|
|
@@ -14520,22 +14737,53 @@ const TableCell = React.memo(({ attribute, row, canEdit, onChange }) => {
|
|
|
14520
14737
|
return jsxRuntime.jsx(CellText, { title: formatted, children: formatted || jsxRuntime.jsx(CellPlaceholder, { children: "\u2014" }) });
|
|
14521
14738
|
}
|
|
14522
14739
|
if (!editing) {
|
|
14523
|
-
return (jsxRuntime.jsx(CellButton, { type: "button",
|
|
14740
|
+
return (jsxRuntime.jsx(CellButton, { type: "button", ...buttonProps, children: formatted || jsxRuntime.jsx(CellPlaceholder, { children: "\u2014" }) }));
|
|
14524
14741
|
}
|
|
14525
14742
|
const renderEditor = () => {
|
|
14526
14743
|
if (type === api.AttributeType.DateTime) {
|
|
14527
|
-
return (jsxRuntime.jsx(uilibGl.DatePicker, { value: dateValue, locale: uilibGl.getLocale(language), withTime: withTime, withHeader: true, width: "100%", onChange: date => handleChange(date.toISOString()) }));
|
|
14744
|
+
return (jsxRuntime.jsx(uilibGl.DatePicker, { focus: true, value: dateValue, locale: uilibGl.getLocale(language), withTime: withTime, withHeader: true, width: "100%", onChange: date => handleChange(date.toISOString()) }));
|
|
14528
14745
|
}
|
|
14529
14746
|
if (NUMBER_EDITOR_TYPES.includes(type)) {
|
|
14530
14747
|
return (jsxRuntime.jsx(uilibGl.NumberInput, { autoFocus: true, value: value === null || value === undefined ? null : Number(value), width: "100%", numeralDecimalScale: INTEGER_ATTRIBUTE_TYPES.includes(type) ? 0 : undefined, onChange: next => handleChange(next) }));
|
|
14531
14748
|
}
|
|
14532
14749
|
return (jsxRuntime.jsx(uilibGl.Input, { autoFocus: true, value: value === null || value === undefined ? "" : String(value), width: "100%", onChange: ({ target }) => handleChange(target.value) }));
|
|
14533
14750
|
};
|
|
14534
|
-
return (jsxRuntime.jsxs(CellEditor, {
|
|
14751
|
+
return (jsxRuntime.jsxs(CellEditor, { ...editorProps, children: [jsxRuntime.jsx(CellGhost, { children: formatted || "—" }), jsxRuntime.jsx(CellField, { children: renderEditor() })] }));
|
|
14535
14752
|
});
|
|
14536
14753
|
|
|
14537
14754
|
const ContainerLoading = () => (jsxRuntime.jsx(uilibGl.Flex, { alignContent: "center", justifyContent: "center", width: "100%", children: jsxRuntime.jsx(uilibGl.CircularProgress, { diameter: 1.5, mono: true }) }));
|
|
14538
14755
|
|
|
14756
|
+
/**
|
|
14757
|
+
* Отсечка тела таблицы по нижней кромке липкой шапки.
|
|
14758
|
+
*
|
|
14759
|
+
* Шапка прозрачная — фон контейнера виден сквозь неё, и красить её нечем: цвет контейнера задаётся
|
|
14760
|
+
* конфигом и меняется. Значит, строки не должны под неё заезжать в принципе. Тело для этого
|
|
14761
|
+
* отсекается (`clip-path`) ровно на то, на сколько шапка его накрыла: низ шапки минус верх тела.
|
|
14762
|
+
*
|
|
14763
|
+
* Разница считается по факту, а не по величине прокрутки: она одинаково верна и когда прокручивает
|
|
14764
|
+
* собственный бокс таблицы, и когда её прокручивает область представления контейнера. Пока
|
|
14765
|
+
* не прокручено, низ шапки совпадает с верхом тела — отсекать нечего.
|
|
14766
|
+
*
|
|
14767
|
+
* Прокрутка не всплывает, но проходит фазу перехвата, поэтому слушаем документ: какой именно
|
|
14768
|
+
* предок прокручивается, знать не нужно.
|
|
14769
|
+
*/
|
|
14770
|
+
const useHeadOverlap = (table) => {
|
|
14771
|
+
React.useLayoutEffect(() => {
|
|
14772
|
+
if (!table) {
|
|
14773
|
+
return undefined;
|
|
14774
|
+
}
|
|
14775
|
+
const update = () => {
|
|
14776
|
+
const head = table.tHead?.getBoundingClientRect();
|
|
14777
|
+
const body = table.tBodies[0]?.getBoundingClientRect();
|
|
14778
|
+
const overlap = head && body ? Math.max(0, head.bottom - body.top) : 0;
|
|
14779
|
+
table.style.setProperty(HEAD_OVERLAP_VARIABLE, `${overlap}px`);
|
|
14780
|
+
};
|
|
14781
|
+
update();
|
|
14782
|
+
document.addEventListener("scroll", update, { capture: true, passive: true });
|
|
14783
|
+
return () => document.removeEventListener("scroll", update, { capture: true });
|
|
14784
|
+
}, [table]);
|
|
14785
|
+
};
|
|
14786
|
+
|
|
14539
14787
|
const compareValues = (left, right, type) => {
|
|
14540
14788
|
if (NUMERIC_ATTRIBUTE_TYPES.includes(type)) {
|
|
14541
14789
|
return Number(left) - Number(right);
|
|
@@ -14580,28 +14828,66 @@ const getNextSort = (current, attributeName) => {
|
|
|
14580
14828
|
};
|
|
14581
14829
|
|
|
14582
14830
|
/**
|
|
14583
|
-
*
|
|
14831
|
+
* Css собственного бокса таблицы: размер из `options` представления плюс прокрутка того,
|
|
14832
|
+
* что в этот размер не влез.
|
|
14833
|
+
*
|
|
14834
|
+
* Размеров нет — нет и бокса: таблица занимает столько, сколько просит содержимое, а лишнее,
|
|
14835
|
+
* как и раньше, уходит в прокрутку области представления контейнера.
|
|
14836
|
+
*
|
|
14837
|
+
* Проценты работают только по ширине. Между областью представления и таблицей стоит блочная
|
|
14838
|
+
* обёртка элемента (`ElementValueWrapper`) с высотой `auto`, и `height: "100%"` в ней не
|
|
14839
|
+
* разрешается — вертикальный предел задают конкретные единицы (`240`, `"20rem"`, `"50vh"`),
|
|
14840
|
+
* а «занять всю выданную высоту» остаётся за `options.height` контейнера.
|
|
14841
|
+
*
|
|
14842
|
+
* `contain: inline-size` идёт в пару к fill-ширине: сама по себе она предком не ограничена
|
|
14843
|
+
* (процент от раскладки, которая меряет себя содержимым, вырождается в `auto`), и таблица
|
|
14844
|
+
* распирала бы ряд плиток или трек `auto` изнутри. С containment вклад бокса в ширину предка
|
|
14845
|
+
* обнуляется: ширину он берёт снаружи, а внутрь по ней не смотрят.
|
|
14846
|
+
*/
|
|
14847
|
+
const getTableBoxStyle = (width, height) => {
|
|
14848
|
+
if (width == null && height == null) {
|
|
14849
|
+
return undefined;
|
|
14850
|
+
}
|
|
14851
|
+
return {
|
|
14852
|
+
...getWrapperSizeStyle({ width, height, overflow: "auto" }),
|
|
14853
|
+
...(isFillSize(width) && { contain: "inline-size" }),
|
|
14854
|
+
};
|
|
14855
|
+
};
|
|
14856
|
+
|
|
14857
|
+
/**
|
|
14858
|
+
* Вид таблицы: колонки плюс локальная сортировка.
|
|
14859
|
+
*
|
|
14860
|
+
* Набор и порядок колонок — это схема контейнера (`attributesDescription`) как есть: показываем
|
|
14861
|
+
* ровно то, что описано, и в том же порядке. Своего отбора у представления нет — иначе описанный
|
|
14862
|
+
* атрибут и видимая колонка расходились бы, а в фильтр всё равно уходят все атрибуты схемы.
|
|
14584
14863
|
*
|
|
14585
14864
|
* Сортировка живёт здесь, а не в контейнере: она ничего не меняет в данных и не должна
|
|
14586
14865
|
* влиять на порядок строк, уходящих в фильтр.
|
|
14866
|
+
*
|
|
14867
|
+
* `width`/`height` — размер собственного бокса таблицы: он ограничивает её саму и прокручивает
|
|
14868
|
+
* то, что не влезло. Место, отведённое контейнеру, задаётся его же опциями и живёт отдельно.
|
|
14587
14869
|
*/
|
|
14588
14870
|
const useTableView = (elementConfig) => {
|
|
14589
14871
|
const context = React.useContext(StructuredDataContext);
|
|
14590
|
-
const {
|
|
14872
|
+
const { sort: sortEnabled, width, height } = elementConfig?.options || {};
|
|
14591
14873
|
const [sort, setSort] = React.useState(null);
|
|
14592
14874
|
const schema = context?.schema;
|
|
14593
14875
|
const rows = context?.rows;
|
|
14594
|
-
const columns = React.useMemo(() =>
|
|
14595
|
-
if (!columnNames?.length) {
|
|
14596
|
-
return schema ?? [];
|
|
14597
|
-
}
|
|
14598
|
-
return columnNames
|
|
14599
|
-
.map(columnName => schema?.find(({ attributeName }) => attributeName === columnName))
|
|
14600
|
-
.filter(Boolean);
|
|
14601
|
-
}, [columnNames, schema]);
|
|
14876
|
+
const columns = React.useMemo(() => schema ?? [], [schema]);
|
|
14602
14877
|
const sortedRows = React.useMemo(() => sortRows(rows ?? [], sort, schema ?? []), [rows, schema, sort]);
|
|
14878
|
+
const sizeCss = React.useMemo(() => getTableBoxStyle(width, height), [height, width]);
|
|
14603
14879
|
const onSortToggle = React.useCallback((attributeName) => setSort(current => getNextSort(current, attributeName)), []);
|
|
14604
|
-
return {
|
|
14880
|
+
return {
|
|
14881
|
+
context,
|
|
14882
|
+
columns,
|
|
14883
|
+
rows: sortedRows,
|
|
14884
|
+
sort,
|
|
14885
|
+
sortEnabled: !!sortEnabled,
|
|
14886
|
+
sizeCss,
|
|
14887
|
+
// Ширина задана — колонки по содержимому: лишнее уходит в прокрутку бокса, а не в многоточие.
|
|
14888
|
+
contentWidth: width != null,
|
|
14889
|
+
onSortToggle,
|
|
14890
|
+
};
|
|
14605
14891
|
};
|
|
14606
14892
|
|
|
14607
14893
|
/**
|
|
@@ -14612,7 +14898,10 @@ const useTableView = (elementConfig) => {
|
|
|
14612
14898
|
*/
|
|
14613
14899
|
const ElementTable = React.memo(({ elementConfig }) => {
|
|
14614
14900
|
const { t } = useGlobalContext();
|
|
14615
|
-
const { context, columns, rows, sort, sortEnabled, onSortToggle } = useTableView(elementConfig);
|
|
14901
|
+
const { context, columns, rows, sort, sortEnabled, sizeCss, contentWidth, onSortToggle } = useTableView(elementConfig);
|
|
14902
|
+
// Узел таблицы держим состоянием, а не ref: отсечка должна встать сразу, как он появится.
|
|
14903
|
+
const [table, setTable] = React.useState(null);
|
|
14904
|
+
useHeadOverlap(table);
|
|
14616
14905
|
if (!context) {
|
|
14617
14906
|
return null;
|
|
14618
14907
|
}
|
|
@@ -14625,7 +14914,10 @@ const ElementTable = React.memo(({ elementConfig }) => {
|
|
|
14625
14914
|
defaultValue: "Схема данных не задана",
|
|
14626
14915
|
}) }));
|
|
14627
14916
|
}
|
|
14628
|
-
return (jsxRuntime.
|
|
14917
|
+
return (jsxRuntime.jsx(TableBox, { "$sizeCss": sizeCss, children: jsxRuntime.jsxs(TableWrapper, { ref: setTable, "$contentWidth": contentWidth, children: [jsxRuntime.jsx(TableHead, { children: jsxRuntime.jsxs("tr", { children: [columns.map(({ attributeName, alias, description }) => {
|
|
14918
|
+
const sorted = sort?.attributeName === attributeName;
|
|
14919
|
+
return (jsxRuntime.jsx(TableHeadCell, { title: description || alias, "$sortable": sortEnabled, onClick: sortEnabled ? () => onSortToggle(attributeName) : undefined, children: jsxRuntime.jsxs(TableHeadContent, { children: [alias, sortEnabled && (jsxRuntime.jsx(TableSortSlot, { "$active": sorted, children: jsxRuntime.jsx(uilibGl.Icon, { kind: sorted && sort?.direction === "desc" ? "sorting_des" : "sorting_asc" }) }))] }) }, attributeName));
|
|
14920
|
+
}), context.canEdit && jsxRuntime.jsx(TableHeadCell, {})] }) }), jsxRuntime.jsx(TableBody, { children: rows.map(row => (jsxRuntime.jsxs(TableRow, { children: [columns.map(attribute => (jsxRuntime.jsx(TableCellWrapper, { children: jsxRuntime.jsx(TableCell, { attribute: attribute, row: row, canEdit: context.canEdit, onChange: context.onCellChange }) }, attribute.attributeName))), context.canEdit && (jsxRuntime.jsx(TableActionsCell, { children: jsxRuntime.jsx(uilibGl.IconButton, { kind: "delete", title: t("actions.delete", { ns: "common", defaultValue: "Удалить" }), onClick: () => context.onRowDelete(row.key) }) }))] }, row.key))) })] }) }));
|
|
14629
14921
|
});
|
|
14630
14922
|
|
|
14631
14923
|
const TooltipIcon = styled(uilibGl.Icon).withConfig({ displayName: "TooltipIcon", componentId: "sc-1lkxudm" }) `
|
|
@@ -15237,7 +15529,8 @@ const getRenderElement = ({ t, config, elementConfig, attributes = [], layerInfo
|
|
|
15237
15529
|
|
|
15238
15530
|
const ElementModal = React.memo(({ type = exports.WidgetType.Dashboard, elementConfig }) => {
|
|
15239
15531
|
const { config } = useWidgetConfig(type);
|
|
15240
|
-
const { expandedContainers, attributes
|
|
15532
|
+
const { expandedContainers, attributes } = useWidgetContext(type);
|
|
15533
|
+
const isDataSourceLoading = useDataSourceLoading(type);
|
|
15241
15534
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
15242
15535
|
const { options } = elementConfig || {};
|
|
15243
15536
|
const { modalId, icon } = options || {};
|
|
@@ -15257,7 +15550,7 @@ const ElementModal = React.memo(({ type = exports.WidgetType.Dashboard, elementC
|
|
|
15257
15550
|
return null;
|
|
15258
15551
|
const { options: modalOptions } = modalConfig;
|
|
15259
15552
|
const { title, maxWidth, minWidth, minHeight } = modalOptions || {};
|
|
15260
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ModalIcon, { kind: icon || "new_window", onClick: handleOpen, children: title }), jsxRuntime.jsxs(uilibGl.Dialog, { maxWidth: maxWidth, minWidth: minWidth, minHeight: minHeight, isOpen: isOpen, modal: true, onCloseRequest: handleClose, style: { paddingBottom: "2rem" }, children: [jsxRuntime.jsx(uilibGl.DialogTitle, { children: jsxRuntime.jsxs(uilibGl.Flex, { justifyContent: "space-between", alignItems: "center", children: [!!title && jsxRuntime.jsx("span", { children: title }), jsxRuntime.jsx(uilibGl.IconButton, { kind: "close", onClick: handleClose })] }) }), jsxRuntime.jsx(uilibGl.DialogContent, { children:
|
|
15553
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ModalIcon, { kind: icon || "new_window", onClick: handleOpen, children: title }), jsxRuntime.jsxs(uilibGl.Dialog, { maxWidth: maxWidth, minWidth: minWidth, minHeight: minHeight, isOpen: isOpen, modal: true, onCloseRequest: handleClose, style: { paddingBottom: "2rem" }, children: [jsxRuntime.jsx(uilibGl.DialogTitle, { children: jsxRuntime.jsxs(uilibGl.Flex, { justifyContent: "space-between", alignItems: "center", children: [!!title && jsxRuntime.jsx("span", { children: title }), jsxRuntime.jsx(uilibGl.IconButton, { kind: "close", onClick: handleClose })] }) }), jsxRuntime.jsx(uilibGl.DialogContent, { children: isDataSourceLoading ? (jsxRuntime.jsx(DashboardLoading, {})) : (jsxRuntime.jsx(Container, { isColumn: true, noBorders: true, children: jsxRuntime.jsx(ContainerChildren, { type: type, items: modalContent, isMain: true, renderElement: renderElement }) })) })] })] }));
|
|
15261
15554
|
});
|
|
15262
15555
|
|
|
15263
15556
|
const elementComponents = {
|
|
@@ -16609,6 +16902,25 @@ const getFilterValue = ({ selectedFilters, configFilters, filterName, newValue,
|
|
|
16609
16902
|
return valueType === "single" ? newValue : valueType === "range" ? [newValue, newValue] : [newValue];
|
|
16610
16903
|
};
|
|
16611
16904
|
|
|
16905
|
+
/** Условие источника задаётся строкой или массивом строк — приводим к массиву для единообразного обхода. */
|
|
16906
|
+
const toConditionsArray = (value) => Array.isArray(value) ? value : value ? [value] : [];
|
|
16907
|
+
|
|
16908
|
+
const MAP_VIEW_PLACEHOLDERS = MAP_VIEW_FILTER_NAMES.map(name => `${FILTER_PREFIX}${name}`);
|
|
16909
|
+
// Та же граница, что и при подстановке в `formatDataSourceCondition`: составные формы
|
|
16910
|
+
// (`%zoomLevel`, `%extent_id`, `%zoom.min`) принадлежат пользовательским фильтрам, не карте.
|
|
16911
|
+
const hasMapViewPlaceholder = (value) => MAP_VIEW_PLACEHOLDERS.some(placeholder => new RegExp(`${placeholder}(?![\\w.])`).test(value));
|
|
16912
|
+
/**
|
|
16913
|
+
* Источники, чей запрос зависит от текущего вида карты — в `parameters` или `condition` у них
|
|
16914
|
+
* есть `%extent` / `%zoom`. Перезапрашивают при движении карты только их: иначе каждый пан
|
|
16915
|
+
* дёргал бы все запросы страницы.
|
|
16916
|
+
*/
|
|
16917
|
+
const getMapViewDataSources = (dataSources) => dataSources?.filter(({ parameters, condition }) => {
|
|
16918
|
+
const hasInParameters = Object.values(parameters ?? {}).some(value => typeof value === "string" && MAP_VIEW_PLACEHOLDERS.includes(value));
|
|
16919
|
+
if (hasInParameters)
|
|
16920
|
+
return true;
|
|
16921
|
+
return toConditionsArray(condition).some(hasMapViewPlaceholder);
|
|
16922
|
+
}) ?? [];
|
|
16923
|
+
|
|
16612
16924
|
const getFormattedAttributes = (t, data, attributes, config) => {
|
|
16613
16925
|
const showOtherItems = config?.options?.otherItems < data?.length;
|
|
16614
16926
|
const otherIndex = config?.options?.otherItems + 1;
|
|
@@ -17185,11 +17497,9 @@ const DashboardLoading = React.memo(() => {
|
|
|
17185
17497
|
});
|
|
17186
17498
|
|
|
17187
17499
|
const Dashboard = React.memo(({ type = exports.WidgetType.Dashboard, noBorders }) => {
|
|
17188
|
-
const
|
|
17189
|
-
const { currentPage } = useWidgetPage(type);
|
|
17500
|
+
const isDataSourceLoading = useDataSourceLoading(type);
|
|
17190
17501
|
const isDiffPage = useDiffPage(type);
|
|
17191
|
-
|
|
17192
|
-
if (dataSourceLoading || isDiffPage) {
|
|
17502
|
+
if (isDataSourceLoading || isDiffPage) {
|
|
17193
17503
|
return (jsxRuntime.jsx(DashboardLoading, {}));
|
|
17194
17504
|
}
|
|
17195
17505
|
return (jsxRuntime.jsx(PagesContainer, { type: type, noBorders: noBorders }));
|
|
@@ -17819,6 +18129,7 @@ const DEFAULT_HEATMAP_STYLE = {
|
|
|
17819
18129
|
exports.ALIGNMENTS = ALIGNMENTS;
|
|
17820
18130
|
exports.ALIGN_ITEMS = ALIGN_ITEMS;
|
|
17821
18131
|
exports.ATTRIBUTE_ICON_ELEMENT_TYPES = ATTRIBUTE_ICON_ELEMENT_TYPES;
|
|
18132
|
+
exports.AddButtonRow = AddButtonRow;
|
|
17822
18133
|
exports.AddFeatureButton = AddFeatureButton;
|
|
17823
18134
|
exports.AddFeatureContainer = AddFeatureContainer;
|
|
17824
18135
|
exports.AlertIconContainer = AlertIconContainer;
|
|
@@ -17920,6 +18231,7 @@ exports.DefaultHeaderContainer = DefaultHeaderContainer;
|
|
|
17920
18231
|
exports.DefaultHeaderWrapper = DefaultHeaderWrapper;
|
|
17921
18232
|
exports.DividerContainer = DividerContainer;
|
|
17922
18233
|
exports.EMPTY_DATA_SOURCE_LAYER_INFO = EMPTY_DATA_SOURCE_LAYER_INFO;
|
|
18234
|
+
exports.EXTENT_FILTER_NAME = EXTENT_FILTER_NAME;
|
|
17923
18235
|
exports.ElementButton = ElementButton;
|
|
17924
18236
|
exports.ElementCamera = ElementCamera;
|
|
17925
18237
|
exports.ElementChart = ElementChart;
|
|
@@ -17954,6 +18266,7 @@ exports.FeatureControls = FeatureControls;
|
|
|
17954
18266
|
exports.FeatureTitleContainer = FeatureTitleContainer;
|
|
17955
18267
|
exports.FiltersContainer = FiltersContainer;
|
|
17956
18268
|
exports.GEOMETRY_ATTRIBUTE = GEOMETRY_ATTRIBUTE;
|
|
18269
|
+
exports.GEOMETRY_FILTER_NAME = GEOMETRY_FILTER_NAME;
|
|
17957
18270
|
exports.GRID_AUTO_FILL_DEFAULTS = GRID_AUTO_FILL_DEFAULTS;
|
|
17958
18271
|
exports.GRID_CELL_ATTR = GRID_CELL_ATTR;
|
|
17959
18272
|
exports.GRID_CELL_ID_PREFIX = GRID_CELL_ID_PREFIX;
|
|
@@ -17989,6 +18302,7 @@ exports.LayersListWrapper = LayersListWrapper;
|
|
|
17989
18302
|
exports.LinearProgressContainer = LinearProgressContainer;
|
|
17990
18303
|
exports.LogTerminal = LogTerminal;
|
|
17991
18304
|
exports.LogoContainer = LogoContainer;
|
|
18305
|
+
exports.MAP_VIEW_FILTER_NAMES = MAP_VIEW_FILTER_NAMES;
|
|
17992
18306
|
exports.MAX_CHART_WIDTH = MAX_CHART_WIDTH;
|
|
17993
18307
|
exports.MAX_TRACKS = MAX_TRACKS;
|
|
17994
18308
|
exports.MIN_TRACK_PX = MIN_TRACK_PX;
|
|
@@ -18046,6 +18360,7 @@ exports.TopContainerButtons = TopContainerButtons;
|
|
|
18046
18360
|
exports.TwoColumnContainer = TwoColumnContainer;
|
|
18047
18361
|
exports.UploadContainer = UploadContainer;
|
|
18048
18362
|
exports.VIEW_MODES = VIEW_MODES;
|
|
18363
|
+
exports.ZOOM_FILTER_NAME = ZOOM_FILTER_NAME;
|
|
18049
18364
|
exports.addDataSource = addDataSource;
|
|
18050
18365
|
exports.addDataSources = addDataSources;
|
|
18051
18366
|
exports.adjustColor = adjustColor;
|
|
@@ -18131,6 +18446,7 @@ exports.getLayerInfo = getLayerInfo;
|
|
|
18131
18446
|
exports.getLayerInfoAttribute = getLayerInfoAttribute;
|
|
18132
18447
|
exports.getLayerInfoFromDataSources = getLayerInfoFromDataSources;
|
|
18133
18448
|
exports.getLayoutChildren = getLayoutChildren;
|
|
18449
|
+
exports.getMapViewDataSources = getMapViewDataSources;
|
|
18134
18450
|
exports.getPagesFromConfig = getPagesFromConfig;
|
|
18135
18451
|
exports.getPagesFromProjectInfo = getPagesFromProjectInfo;
|
|
18136
18452
|
exports.getProxyService = getProxyService;
|
|
@@ -18189,6 +18505,7 @@ exports.sizeCssMixin = sizeCssMixin;
|
|
|
18189
18505
|
exports.sliceShownOtherItems = sliceShownOtherItems;
|
|
18190
18506
|
exports.stretchPalette = stretchPalette;
|
|
18191
18507
|
exports.timeOptions = timeOptions;
|
|
18508
|
+
exports.toConditionsArray = toConditionsArray;
|
|
18192
18509
|
exports.toCssSize = toCssSize;
|
|
18193
18510
|
exports.toFrSize = toFrSize;
|
|
18194
18511
|
exports.toPxNumber = toPxNumber;
|
|
@@ -18211,6 +18528,7 @@ exports.useContainerRoot = useContainerRoot;
|
|
|
18211
18528
|
exports.useCurrentPageLayers = useCurrentPageLayers;
|
|
18212
18529
|
exports.useCustomFeatureSelect = useCustomFeatureSelect;
|
|
18213
18530
|
exports.useDashboardHeader = useDashboardHeader;
|
|
18531
|
+
exports.useDataSourceLoading = useDataSourceLoading;
|
|
18214
18532
|
exports.useDataSources = useDataSources;
|
|
18215
18533
|
exports.useDebouncedCallback = useDebouncedCallback;
|
|
18216
18534
|
exports.useDiffPage = useDiffPage;
|