@evergis/react 4.0.44 → 4.0.46

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/index.js CHANGED
@@ -12,14 +12,14 @@ var turf = require('@turf/turf');
12
12
  var dateFns = require('date-fns');
13
13
  var lodash = require('lodash');
14
14
  var locale = require('date-fns/locale');
15
- var signalr = require('@microsoft/signalr');
16
15
  var findAnd = require('find-and');
17
- var jspdf = require('jspdf');
18
- var html2canvas = require('html2canvas');
16
+ var signalr = require('@microsoft/signalr');
19
17
  var MapboxDraw = require('@mapbox/mapbox-gl-draw');
20
18
  var MapGL = require('react-map-gl/maplibre');
21
19
  require('@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css');
22
20
  require('mapbox-gl/dist/mapbox-gl.css');
21
+ var jspdf = require('jspdf');
22
+ var html2canvas = require('html2canvas');
23
23
  var react = require('swiper/react');
24
24
  var ReactMarkdown = require('react-markdown');
25
25
  var rehypeRaw = require('rehype-raw');
@@ -3961,880 +3961,461 @@ const useAutoCompleteControl = (items) => {
3961
3961
  };
3962
3962
  };
3963
3963
 
3964
- const useFetchWithAuth = (url, transform, cleanup) => {
3965
- const [data, setData] = React.useState(null);
3966
- const loadingRef = React.useRef(false);
3967
- const transformRef = React.useRef(transform);
3968
- const cleanupRef = React.useRef(cleanup);
3969
- transformRef.current = transform;
3970
- cleanupRef.current = cleanup;
3971
- const fetchData = React.useCallback(() => {
3972
- if (!url || loadingRef.current)
3973
- return;
3974
- loadingRef.current = true;
3975
- const token = window.localStorage.getItem(api.STORAGE_TOKEN_KEY);
3976
- fetch(url, {
3977
- headers: token ? { Authorization: `Bearer ${token}` } : {},
3978
- })
3979
- .then(res => (res.ok ? transformRef.current(res) : null))
3980
- .then(setData)
3981
- .catch(() => {
3982
- setData(null);
3983
- })
3984
- .finally(() => {
3985
- loadingRef.current = false;
3986
- });
3987
- }, [url]);
3988
- React.useEffect(() => {
3989
- if (url) {
3990
- fetchData();
3991
- }
3992
- else {
3993
- setData(null);
3994
- }
3995
- }, [url]);
3996
- React.useEffect(() => () => {
3997
- if (data !== null)
3998
- cleanupRef.current?.(data);
3999
- }, [data]);
4000
- return data;
4001
- };
4002
-
4003
- const toObjectUrl = (res) => res.blob().then(blob => URL.createObjectURL(blob));
4004
- const useFetchImageWithAuth = (url) => useFetchWithAuth(url, toObjectUrl, URL.revokeObjectURL);
3964
+ const eqlParametersToPayload = (parameters) => Object.keys(parameters).reduce((result, paramName) => ({
3965
+ ...result,
3966
+ [paramName]: parameters[paramName].default,
3967
+ }), {});
4005
3968
 
4006
- const DashboardContext = React.createContext({});
4007
- const DashboardProvider = React.memo(({ children, ...props }) => {
4008
- return jsxRuntime.jsx(DashboardContext.Provider, { value: props, children: children });
4009
- });
3969
+ const addDataSource = (dashboardConfiguration, pageIndex, query, additional) => {
3970
+ const newConfig = JSON.parse(JSON.stringify(dashboardConfiguration));
3971
+ if (!newConfig.children[0].children[pageIndex].dataSources) {
3972
+ newConfig.children[0].children[pageIndex].dataSources = [];
3973
+ }
3974
+ const freeId = newConfig.children[0].children[pageIndex].dataSources.length + 1;
3975
+ const parameters = eqlParametersToPayload(additional.parameters);
3976
+ newConfig.children[0].children[pageIndex].dataSources.push({
3977
+ name: `datasource_${freeId}`,
3978
+ query,
3979
+ parameters,
3980
+ offset: 0,
3981
+ limit: DEFAULT_DATA_SOURCE_LIMIT,
3982
+ });
3983
+ return newConfig.children;
3984
+ };
4010
3985
 
4011
- const FeatureCardContext = React.createContext({});
4012
- const FeatureCardProvider = React.memo(({ children, ...props }) => {
4013
- return jsxRuntime.jsx(FeatureCardContext.Provider, { value: props, children: children });
4014
- });
3986
+ const addDataSources = (dashboardConfiguration, pageIndex, layerNames) => {
3987
+ const newConfig = JSON.parse(JSON.stringify(dashboardConfiguration));
3988
+ if (!newConfig.children[0].children[pageIndex].dataSources) {
3989
+ newConfig.children[0].children[pageIndex].dataSources = [];
3990
+ }
3991
+ layerNames.forEach(layerName => {
3992
+ newConfig.children[0].children[pageIndex].dataSources.push({
3993
+ name: layerName.split(".")[1],
3994
+ layerName,
3995
+ query: "",
3996
+ parameters: {},
3997
+ });
3998
+ });
3999
+ return newConfig.children;
4000
+ };
4015
4001
 
4016
- const GlobalContext = React.createContext({});
4017
- const GlobalProvider = React.memo(({ children, ...props }) => {
4018
- return jsxRuntime.jsx(GlobalContext.Provider, { value: props, children: children });
4019
- });
4002
+ const getConfigFilter = (filterName, configFilters) => configFilters?.find(({ name }) => name === filterName);
4020
4003
 
4021
- const MapContext = React.createContext({});
4004
+ const getDataSource = (dataSourceName, dataSources) => dataSources?.find(({ name }) => name === dataSourceName);
4022
4005
 
4023
- const MapProvider = ({ basemapItems, defaultBasemap, children }) => {
4024
- const map = React.useRef();
4025
- const draw = React.useRef();
4026
- const [loaded, setLoaded] = React.useState(false);
4027
- const [basemapName, setBasemapName] = React.useState(defaultBasemap);
4028
- return (jsxRuntime.jsx(MapContext.Provider, { value: {
4029
- map,
4030
- draw,
4031
- loaded,
4032
- setLoaded,
4033
- basemapItems,
4034
- basemapName,
4035
- setBasemapName,
4036
- defaultBasemap,
4037
- }, children: children }));
4006
+ const getDataSourceFilterValue = ({ filterName, filterProp, attributeAlias, dataSource, selectedFilters, }) => {
4007
+ if (lodash.isNil(selectedFilters[filterName]))
4008
+ return null;
4009
+ const feature = dataSource?.features?.find(({ properties }) => properties[attributeAlias] ===
4010
+ (Array.isArray(selectedFilters[filterName].value)
4011
+ ? selectedFilters[filterName].value[0]
4012
+ : selectedFilters[filterName].value));
4013
+ return feature?.properties?.[filterProp];
4038
4014
  };
4039
4015
 
4040
- exports.BaseMapTheme = void 0;
4041
- (function (BaseMapTheme) {
4042
- BaseMapTheme["Light"] = "light";
4043
- BaseMapTheme["Dark"] = "dark";
4044
- })(exports.BaseMapTheme || (exports.BaseMapTheme = {}));
4045
-
4046
- const ServerNotificationsContext = React.createContext({});
4016
+ const getSelectedFilterValue = (filterName, selectedFilters, defaultValue) => {
4017
+ return ((!lodash.isNil(selectedFilters?.[filterName]?.value) &&
4018
+ Array.isArray(defaultValue) &&
4019
+ !Array.isArray(selectedFilters[filterName].value)
4020
+ ? [selectedFilters[filterName].value]
4021
+ : selectedFilters?.[filterName]?.value) ?? defaultValue);
4022
+ };
4047
4023
 
4048
- const useServerNotifications = (url, initialized, apiClient) => {
4049
- const hubConnection = React.useRef(null);
4050
- const [connection, setConnection] = React.useState(null);
4051
- const subscribeNotifications = React.useCallback(() => {
4052
- if (!connection || connection.state !== "Connected") {
4053
- return;
4024
+ const applyQueryFilters = ({ parameters: configParameters, filters: configFilters, selectedFilters, geometry, attributes, layerInfo, dataSources, projectDataSources, }) => {
4025
+ if (!configParameters) {
4026
+ return {};
4027
+ }
4028
+ return Object.keys(configParameters).reduce((result, key) => {
4029
+ if (typeof configParameters[key] === "string" && configParameters[key].startsWith(PROVIDER_PREFIX)) {
4030
+ const raw = configParameters[key].slice(PROVIDER_PREFIX.length);
4031
+ const [provider, layerName, fieldName] = raw.split(":");
4032
+ if (provider === exports.ProviderPrefix.Card && fieldName && layerInfo.name === layerName && attributes) {
4033
+ const attribute = attributes.find(({ attributeName }) => attributeName === fieldName);
4034
+ const cardValue = attribute?.value ?? null;
4035
+ if (lodash.isNil(cardValue))
4036
+ return result;
4037
+ return { ...result, [key]: cardValue };
4038
+ }
4039
+ if (provider === exports.ProviderPrefix.Left && layerName && fieldName && projectDataSources?.length) {
4040
+ const dataSource = projectDataSources.find(({ layerName: name }) => name === layerName);
4041
+ const leftValue = dataSource?.features?.[0]?.properties?.[fieldName] ?? null;
4042
+ if (lodash.isNil(leftValue))
4043
+ return result;
4044
+ return { ...result, [key]: leftValue };
4045
+ }
4046
+ return result;
4054
4047
  }
4055
- connection
4056
- .invoke("SubscribeNotifications", [])
4057
- .then(() => console.info("Подписка `SubscribeNotifications` оформлена"))
4058
- .catch(err => console.info("Ошибка подписки `SubscribeNotifications`:", err));
4059
- }, [connection]);
4060
- React.useEffect(() => {
4061
- if (!initialized) {
4062
- return;
4048
+ if (typeof configParameters[key] === "string" && configParameters[key].includes("{") && attributes?.length) {
4049
+ const exactAttr = attributes.find(({ attributeName }) => configParameters[key] === `{${attributeName}}`);
4050
+ if (exactAttr) {
4051
+ return { ...result, [key]: exactAttr.value ?? "" };
4052
+ }
4053
+ let interpolated = configParameters[key];
4054
+ attributes.forEach(({ attributeName, value: attrValue }) => {
4055
+ interpolated = interpolated.replace(new RegExp(`\\{${attributeName}\\}`, "g"), attrValue?.toString() ?? "");
4056
+ });
4057
+ return { ...result, [key]: interpolated };
4063
4058
  }
4064
- hubConnection.current = new signalr.HubConnectionBuilder()
4065
- .withUrl(`${url}?clientId=${api.generateId()}`, {
4066
- withCredentials: true,
4067
- skipNegotiation: true,
4068
- transport: signalr.HttpTransportType.WebSockets,
4069
- accessTokenFactory: async () => {
4070
- let accessToken = window.localStorage.getItem(api.STORAGE_TOKEN_KEY) || "";
4071
- const { exp } = api.parseJwt(accessToken);
4072
- const currentTime = new Date().getTime() / 1000;
4073
- if (currentTime > exp) {
4074
- const refreshToken = window.localStorage.getItem(api.STORAGE_REFRESH_TOKEN_KEY);
4075
- if (refreshToken) {
4076
- const refreshTokenResponse = await apiClient.account.refreshToken({
4077
- refreshToken,
4078
- });
4079
- if (refreshTokenResponse) {
4080
- accessToken = refreshTokenResponse.token;
4081
- window.localStorage.setItem(api.STORAGE_TOKEN_KEY, refreshTokenResponse.token);
4082
- window.localStorage.setItem(api.STORAGE_REFRESH_TOKEN_KEY, refreshTokenResponse.refreshToken);
4083
- }
4084
- }
4085
- else {
4086
- await apiClient.logout();
4087
- }
4088
- }
4089
- return accessToken;
4090
- },
4091
- })
4092
- .withAutomaticReconnect()
4093
- .configureLogging(signalr.LogLevel.Information)
4094
- .build();
4095
- hubConnection.current
4096
- .start()
4097
- .then(() => console.info("Серверные нотификации подключены"))
4098
- .catch(err => console.info("Ошибка:", err))
4099
- .finally(() => setConnection(hubConnection.current));
4100
- }, [initialized]); // eslint-disable-line
4101
- React.useEffect(() => {
4102
- if (!connection || connection.state !== "Connected") {
4103
- return;
4059
+ if (typeof configParameters[key] !== "string" || !configParameters[key].startsWith("%")) {
4060
+ return {
4061
+ ...result,
4062
+ [key]: configParameters[key],
4063
+ };
4104
4064
  }
4105
- connection.onreconnecting(() => console.info("Переподключение к серверным нотификациям"));
4106
- connection.onreconnected(subscribeNotifications);
4107
- subscribeNotifications();
4108
- }, [connection]);
4109
- return connection;
4110
- };
4111
-
4112
- const ServerNotificationsProvider = ({ url, initialized, apiClient, children }) => {
4113
- const connection = useServerNotifications(url, initialized, apiClient);
4114
- const addSubscription = React.useCallback(async (payload) => {
4115
- if (!connection || connection.state !== "Connected" || !payload) {
4116
- return;
4065
+ const filterFullName = configParameters[key].replace("%", "");
4066
+ const [filterName, filterProp] = filterFullName.includes(".") ? filterFullName.split(".") : [filterFullName, null];
4067
+ const { defaultValue, relatedDataSource, attributeAlias } = getConfigFilter(filterName, configFilters) || {};
4068
+ if (filterName === "geometry" && geometry && !geometry.includes("()") && geometry.endsWith(")")) {
4069
+ return {
4070
+ ...result,
4071
+ [key]: geometry,
4072
+ };
4117
4073
  }
4118
- try {
4119
- const id = await connection.invoke("AddSubscription", payload);
4120
- console.info("Подписка добавлена, id:", id);
4121
- return id;
4074
+ if (configParameters[key].endsWith(".max")) {
4075
+ return {
4076
+ ...result,
4077
+ [key]: selectedFilters?.[filterName]?.max ?? (Array.isArray(defaultValue) ? defaultValue[1] : defaultValue),
4078
+ };
4122
4079
  }
4123
- catch (err) {
4124
- console.info("Ошибка добавления подписки:", err);
4125
- return Promise.resolve(null);
4126
- }
4127
- }, [connection]);
4128
- const updateSubscription = React.useCallback(async (id, payload) => {
4129
- if (!connection || connection.state !== "Connected" || !id || !payload) {
4130
- return;
4131
- }
4132
- try {
4133
- await connection.invoke("UpdateSubscription", id, payload);
4134
- }
4135
- catch (err) {
4136
- console.info(`Ошибка обновления подписки ${id}:`, err);
4137
- }
4138
- }, [connection]);
4139
- const unsubscribeById = React.useCallback(async (id) => {
4140
- if (!connection || connection.state !== "Connected" || !id) {
4141
- return;
4080
+ if (configParameters[key].endsWith(".min")) {
4081
+ return {
4082
+ ...result,
4083
+ [key]: selectedFilters?.[filterName]?.min ?? (Array.isArray(defaultValue) ? defaultValue[0] : defaultValue),
4084
+ };
4142
4085
  }
4143
- try {
4144
- await connection.invoke("Unsubscribe", [id]);
4086
+ if (configParameters[key].includes(".")) {
4087
+ return {
4088
+ ...result,
4089
+ [key]: getDataSourceFilterValue({
4090
+ filterName,
4091
+ filterProp,
4092
+ selectedFilters,
4093
+ attributeAlias,
4094
+ dataSource: getDataSource(relatedDataSource, dataSources),
4095
+ }) ?? defaultValue,
4096
+ };
4145
4097
  }
4146
- catch (err) {
4147
- console.info(`Ошибка отписки по ${id}:`, err);
4098
+ const value = getSelectedFilterValue(filterName, selectedFilters, defaultValue);
4099
+ if (lodash.isNil(value)) {
4100
+ return result;
4148
4101
  }
4149
- }, [connection]);
4150
- return (jsxRuntime.jsx(ServerNotificationsContext.Provider, { value: {
4151
- connection,
4152
- addSubscription,
4153
- updateSubscription,
4154
- unsubscribeById,
4155
- }, children: children }));
4102
+ return {
4103
+ ...result,
4104
+ [key]: value,
4105
+ };
4106
+ }, {});
4156
4107
  };
4157
4108
 
4158
- const useWidgetContext = (type = exports.WidgetType.Dashboard) => {
4159
- const { toggleLayersVisibility, visibleLayers, projectInfo, updateProject, layerInfos, geometryFilter, dashboardLayers, setDashboardLayer, components: dashboardComponents, config: dashboardConfig, containerIds, pageIndex: projectPageIndex, selectedTabId: projectSelectedTabId, setSelectedTabId: setProjectSelectedTabId, dataSources: projectDataSources, loading: projectLoading, editMode: projectEditMode, filters: projectFilters, changeFilters: projectChangeFilters, expandContainer: projectExpandContainer, expandedContainers: projectExpandedContainers, nextPage: projectNextPage, prevPage: projectPrevPage, changePage: projectChangePage, } = React.useContext(DashboardContext) || {};
4160
- const { layerInfo, attributes, feature, controls, changeControls, closeFeatureCard, config: featureConfig, pageIndex: featurePageIndex, selectedTabId: featureSelectedTabId, setSelectedTabId: setFeatureSelectedTabId, dataSources: featureDataSources, loading: featureLoading, editMode: featureEditMode, filters: featureFilters, changeFilters: featureChangeFilters, expandContainer: featureExpandContainer, expandedContainers: featureExpandedContainers, nextPage: featureNextPage, prevPage: featurePrevPage, changePage: featureChangePage, } = React.useContext(FeatureCardContext) || {};
4109
+ const checkEqualOrIncludes = (arrayOrSingle, value) => arrayOrSingle && Array.isArray(arrayOrSingle) ? arrayOrSingle.includes(value) : arrayOrSingle === value;
4110
+
4111
+ const checkIsLoading = (dataSources, config, filters) => config?.children?.some(child => {
4112
+ const filter = filters?.find(({ name }) => name === child.options?.filterName);
4113
+ return (!!filter && !!child.options?.filterName && !dataSources?.some(({ name }) => name === filter.relatedDataSource));
4114
+ });
4115
+
4116
+ const createConfigLayer = (layerName) => ({
4117
+ name: layerName,
4118
+ query: "",
4119
+ parameters: {},
4120
+ opacity: 1,
4121
+ selectable: false,
4122
+ filterZoomTo: false,
4123
+ isVisible: true,
4124
+ });
4125
+
4126
+ const createNewPageId = (pages) => {
4127
+ if (!pages?.length)
4128
+ return 1;
4129
+ const maxPageId = pages.reduce((maxId, page) => {
4130
+ const pageId = parseInt(page.id.replace(CONFIG_PAGE_ID, ""), 10);
4131
+ return Math.max(maxId, pageId);
4132
+ }, 0);
4133
+ return maxPageId + 1;
4134
+ };
4135
+
4136
+ const createConfigPage = (props) => {
4137
+ const { title, defaultTitle, pages, baseMapName, position, resolution /* , srid*/ } = props || {};
4138
+ const pageId = createNewPageId(pages);
4161
4139
  return {
4162
- toggleLayersVisibility,
4163
- visibleLayers,
4164
- projectInfo,
4165
- layerInfos,
4166
- updateProject,
4167
- dashboardLayers,
4168
- setDashboardLayer,
4169
- geometryFilter,
4170
- layerInfo,
4171
- attributes,
4172
- feature,
4173
- closeFeatureCard,
4174
- containerIds,
4175
- controls,
4176
- changeControls,
4177
- components: dashboardComponents,
4178
- config: type === exports.WidgetType.Dashboard ? dashboardConfig : featureConfig,
4179
- isEditing: type === exports.WidgetType.Dashboard ? projectEditMode : featureEditMode,
4180
- isLoading: type === exports.WidgetType.Dashboard ? projectLoading : featureLoading,
4181
- pageIndex: type === exports.WidgetType.Dashboard ? projectPageIndex || 1 : featurePageIndex || 1,
4182
- filters: type === exports.WidgetType.Dashboard ? projectFilters : featureFilters,
4183
- changeFilters: type === exports.WidgetType.Dashboard ? projectChangeFilters : featureChangeFilters,
4184
- dataSources: type === exports.WidgetType.Dashboard ? projectDataSources : featureDataSources,
4185
- expandContainer: type === exports.WidgetType.Dashboard ? projectExpandContainer : featureExpandContainer,
4186
- expandedContainers: type === exports.WidgetType.Dashboard ? projectExpandedContainers : featureExpandedContainers,
4187
- selectedTabId: type === exports.WidgetType.Dashboard ? projectSelectedTabId : featureSelectedTabId,
4188
- setSelectedTabId: type === exports.WidgetType.Dashboard ? setProjectSelectedTabId : setFeatureSelectedTabId,
4189
- nextPage: type === exports.WidgetType.Dashboard ? projectNextPage : featureNextPage,
4190
- prevPage: type === exports.WidgetType.Dashboard ? projectPrevPage : featurePrevPage,
4191
- changePage: type === exports.WidgetType.Dashboard ? projectChangePage : featureChangePage,
4140
+ id: `${CONFIG_PAGE_ID}${pageId}`,
4141
+ templateName: exports.ContainerTemplate.ContainersGroup,
4142
+ children: [],
4143
+ layers: [],
4144
+ dataSources: [],
4145
+ filters: [],
4146
+ tasks: [],
4147
+ style: {},
4148
+ options: {
4149
+ title: title || [defaultTitle, pageId].filter(Boolean).join(" "),
4150
+ position: position || [DEFAULT_LNG, DEFAULT_LAT],
4151
+ resolution: resolution || DEFAULT_ZOOM,
4152
+ baseMapName: baseMapName || DEFAULT_BASE_MAP,
4153
+ baseMapSettings: {},
4154
+ expandedLayers: true,
4155
+ // srid: srid || null,
4156
+ },
4192
4157
  };
4193
4158
  };
4194
4159
 
4195
- const useGlobalContext = () => {
4196
- const { t, language, themeName, api, ewktGeometry } = React.useContext(GlobalContext) || {};
4197
- const translate = React.useCallback((value, options) => {
4198
- if (t)
4199
- return t(value, options);
4200
- return options?.defaultValue ?? value;
4201
- }, [t]);
4202
- return React.useMemo(() => ({
4203
- t: translate,
4204
- language,
4205
- themeName,
4206
- api,
4207
- ewktGeometry,
4208
- }), [language, translate, api, ewktGeometry, themeName]);
4160
+ const getAttributesConfiguration = (layer) => {
4161
+ const layerAttributeConfiguration = layer?.configuration?.attributesConfiguration ?? {};
4162
+ const { geometryAttribute, idAttribute } = layerAttributeConfiguration;
4163
+ const emptyLayerDefinition = {
4164
+ attributes: [],
4165
+ geometryAttribute: geometryAttribute ?? GEOMETRY_ATTRIBUTE,
4166
+ idAttribute: idAttribute ?? DEFAULT_ID_ATTRIBUTE_NAME,
4167
+ titleAttribute: "",
4168
+ };
4169
+ if (!isLayerService(layer)) {
4170
+ return emptyLayerDefinition;
4171
+ }
4172
+ return {
4173
+ ...emptyLayerDefinition,
4174
+ ...layerAttributeConfiguration,
4175
+ attributes: layerAttributeConfiguration?.attributes ||
4176
+ emptyLayerDefinition.attributes,
4177
+ };
4209
4178
  };
4210
4179
 
4211
- const HEIGHT_OFFSET = 20;
4212
- const FILL_OPACITY = 0.28;
4213
- const lineGenerator = line()
4214
- .x(d => d[0])
4215
- .y(d => d[1])
4216
- .curve(monotoneX);
4217
- const areaGenerator = (height) => area()
4218
- .defined(d => d !== null)
4219
- .x(d => d[0])
4220
- .y0(height - HEIGHT_OFFSET)
4221
- .y1(d => d[1])
4222
- .curve(monotoneX);
4223
- function getLinePoints(numPoints, svgLine) {
4224
- if (!svgLine?.getTotalLength) {
4225
- return [];
4226
- }
4227
- const lineLength = svgLine.getTotalLength();
4228
- let interval;
4229
- if (numPoints === 1) {
4230
- interval = 0;
4180
+ const formatChartRelatedValue = (t, value, layerInfo, relatedAttributes) => {
4181
+ const layerDefinition = getAttributesConfiguration(layerInfo);
4182
+ const relatedAxis = relatedAttributes.find(({ chartAxis }) => chartAxis === "y");
4183
+ const attribute = layerDefinition.attributes[relatedAxis.attributeName];
4184
+ return attribute ? formatAttributeValue({ t, type: attribute.type, value, stringFormat: attribute.stringFormat }) : value;
4185
+ };
4186
+
4187
+ const isEmptyValue = (value) => value === "" || value === null || value === undefined;
4188
+
4189
+ const checkIsDateFormat = (value) => value.startsWith("#'") && value.endsWith("'");
4190
+ const checkIfNeedQuotes = (value, attributeType) => {
4191
+ return attributeType
4192
+ ? !NUMERIC_ATTRIBUTE_TYPES.includes(attributeType)
4193
+ : typeof value === "number"
4194
+ ? false
4195
+ : typeof value === "string" && !isNumeric(value) && !checkIsDateFormat(value);
4196
+ };
4197
+ const formatConditionValue = ({ value, attributeType, defaultValue, checkQuotes = true, isSetParams = false, }) => {
4198
+ if (isEmptyValue(value))
4199
+ return "";
4200
+ const formattedValue = Array.isArray(defaultValue) && !Array.isArray(value) ? [value] : value;
4201
+ const isArray = Array.isArray(formattedValue);
4202
+ let stringValue = formattedValue.toString();
4203
+ if (isArray) {
4204
+ return formattedValue.length && !formattedValue.every(item => !item)
4205
+ ? `[${formattedValue
4206
+ .map(item => (checkQuotes && checkIfNeedQuotes(item, attributeType) ? `'${item}'` : item))
4207
+ .join(",")}]`
4208
+ : "";
4231
4209
  }
4232
- else {
4233
- interval = lineLength / (numPoints - 1);
4210
+ if (isSetParams && checkIsDateFormat(stringValue)) {
4211
+ stringValue = stringValue.slice(2, -1);
4234
4212
  }
4235
- return range(numPoints)
4236
- .filter(d => d * interval)
4237
- .map(d => {
4238
- const value = d * interval;
4239
- const { x, y } = svgLine.getPointAtLength(value);
4240
- return [x, y];
4213
+ return checkQuotes && checkIfNeedQuotes(stringValue, attributeType) ? `'${stringValue}'` : stringValue;
4214
+ };
4215
+
4216
+ const applyFiltersToCondition = ({ condition, name, defaultValue, filters, isSingle, isSetParams, }) => {
4217
+ const hasFilter = filters[name] !== undefined && !!filters[name].value?.toString().length;
4218
+ const min = formatConditionValue({
4219
+ value: hasFilter ? filters[name].min : null,
4220
+ checkQuotes: !isSingle,
4221
+ isSetParams,
4241
4222
  });
4242
- }
4243
- function wrap() {
4244
- const width = 80;
4245
- const padding = 10;
4246
- const self = select(this);
4247
- let textLength = self.node().getComputedTextLength();
4248
- let text = self.text();
4249
- while (textLength > width - 2 * padding && text.length > 0) {
4250
- text = text.slice(0, -1);
4251
- self.text(`${text}...`);
4252
- textLength = self.node().getComputedTextLength();
4253
- }
4254
- }
4255
- const useChartChange = ({ dataSources, chartId, width, height, relatedAttributes, defaultColor, fontColor, markers, showMarkers, }) => {
4256
- const { t } = useGlobalContext();
4257
- const { layerInfos } = useWidgetContext();
4258
- const strokeColors = relatedAttributes
4259
- .filter(({ chartAxis }) => chartAxis === "y")
4260
- .map(({ axisColor }) => axisColor);
4261
- const ref = React.useRef({
4262
- path: null,
4263
- area: null,
4264
- points: [],
4223
+ const max = formatConditionValue({
4224
+ value: hasFilter ? filters[name].max : null,
4225
+ checkQuotes: !isSingle,
4226
+ isSetParams,
4265
4227
  });
4266
- const onChange = React.useCallback((range) => {
4267
- const { path, area, points } = ref.current;
4268
- let filteredPoints = [...points];
4269
- if (range) {
4270
- const hundred = 100;
4271
- const x1 = +width * (range[0] / hundred);
4272
- const x2 = +width * (range[1] / hundred);
4273
- filteredPoints = points.filter(([x]) => x >= x1 && x <= x2);
4228
+ const value = formatConditionValue({
4229
+ value: hasFilter ? filters[name].value : filters[name] === undefined ? defaultValue : "",
4230
+ defaultValue,
4231
+ checkQuotes: !isSingle,
4232
+ isSetParams,
4233
+ });
4234
+ const filterName = `${FILTER_PREFIX}${name}`;
4235
+ const hasMin = min !== "" && min !== null && min !== undefined;
4236
+ const hasMax = max !== "" && max !== null && max !== undefined;
4237
+ let result = condition;
4238
+ if (hasMin || hasMax) {
4239
+ if (hasMin) {
4240
+ result = result.replace(new RegExp(`${filterName}\\.min`), min);
4241
+ }
4242
+ if (hasMax) {
4243
+ result = result.replace(new RegExp(`${filterName}\\.max`), max);
4274
4244
  }
4275
- path && path.attr("d", lineGenerator(filteredPoints));
4276
- area && area.attr("d", areaGenerator(height)(filteredPoints));
4277
- }, [height, width]);
4278
- const customize = React.useCallback(({ svg }) => {
4279
- svg.style("overflow", "visible");
4280
- svg
4281
- .selectAll(`.${charts.lineChartClassNames.lineChartXScaleGlobal} line,
4282
- .${charts.lineChartClassNames.lineChartYScaleGlobal} line,
4283
- .domain`)
4284
- .each((_, index, nodes) => {
4285
- nodes[index].remove();
4286
- });
4287
- svg
4288
- .selectAll(`.${charts.lineChartClassNames.lineChartXScaleGlobal} .tick`)
4289
- .each((_, index, nodes) => {
4290
- if (!index) {
4291
- nodes[index].style.textAnchor = "start";
4292
- }
4293
- if (index === nodes.length - 1) {
4294
- nodes[index].style.textAnchor = "end";
4295
- }
4296
- if (markers) {
4297
- nodes[index].remove();
4298
- }
4299
- if (showMarkers) {
4300
- if (index % showMarkers !== 0) {
4301
- nodes[index].remove();
4302
- }
4303
- }
4304
- else {
4305
- if (index && index < nodes.length - 1) {
4306
- nodes[index].remove();
4307
- }
4308
- }
4309
- });
4310
- svg
4311
- .selectAll(`.${charts.lineChartClassNames.lineChartXScaleGlobal} .tick text`)
4312
- .attr("y", 16);
4313
- svg
4314
- .selectAll(`.${charts.lineChartClassNames.lineChartYScaleGlobal} .tick text`)
4315
- .text(item => {
4316
- if (!item)
4317
- return 0;
4318
- const dataSource = dataSources?.find(({ name }) => name === relatedAttributes[0].dataSourceName);
4319
- const layerInfo = dataSource
4320
- ? layerInfos.find(({ name }) => name === dataSource.layerName)
4321
- : null;
4322
- const attribute = layerInfo?.configuration?.attributesConfiguration?.attributes[relatedAttributes[0].attributeName];
4323
- return attribute
4324
- ? formatAttributeValue({
4325
- t,
4326
- type: attribute.type,
4327
- value: item,
4328
- stringFormat: attribute.stringFormat,
4329
- noUnits: true,
4330
- })
4331
- : item;
4332
- })
4333
- .each(wrap);
4334
- svg.selectAll(".tick text").attr("style", `color: ${fontColor}`);
4335
- const global = svg.select(`.${charts.lineChartClassNames.lineChartLinesGlobal}`);
4336
- const lineChartLines = svg.selectAll(`.${charts.lineChartClassNames.lineChartLine}`);
4337
- const defs = [];
4338
- lineChartLines.each((_, index, nodes) => {
4339
- const lineChartLine = nodes[index];
4340
- const gradientId = lodash.uniqueId(`${chartId}-gradient-`);
4341
- const color = strokeColors[index] || defaultColor;
4342
- const newPath = global
4343
- .append("path")
4344
- .attr("stroke", color)
4345
- .attr("stroke-width", "0")
4346
- .attr("class", charts.lineChartClassNames.lineChartLine);
4347
- const points = getLinePoints(+width, lineChartLine);
4348
- const area = global
4349
- .append("path")
4350
- .attr("fill", `url(#${gradientId})`)
4351
- .attr("stroke-width", "0")
4352
- .attr("fill-opacity", FILL_OPACITY);
4353
- defs.push(`
4354
- <linearGradient id="${gradientId}" x1="0" y1="0" x2="0" y2="1">
4355
- <stop offset="0" stop-color="${color}" stop-opacity="1" />
4356
- <stop offset="1" stop-color="${color}" stop-opacity="0" />
4357
- </linearGradient>
4358
- `);
4359
- ref.current = {
4360
- path: newPath,
4361
- area,
4362
- points,
4363
- };
4364
- onChange();
4365
- });
4366
- svg.append("defs").html(() => defs.join(""));
4367
- }, [
4368
- fontColor,
4369
- dataSources,
4370
- layerInfos,
4371
- relatedAttributes,
4372
- chartId,
4373
- strokeColors,
4374
- defaultColor,
4375
- width,
4376
- onChange,
4377
- showMarkers,
4378
- markers,
4379
- ]);
4380
- return [customize, onChange];
4381
- };
4382
-
4383
- const eqlParametersToPayload = (parameters) => Object.keys(parameters).reduce((result, paramName) => ({
4384
- ...result,
4385
- [paramName]: parameters[paramName].default,
4386
- }), {});
4387
-
4388
- const addDataSource = (dashboardConfiguration, pageIndex, query, additional) => {
4389
- const newConfig = JSON.parse(JSON.stringify(dashboardConfiguration));
4390
- if (!newConfig.children[0].children[pageIndex].dataSources) {
4391
- newConfig.children[0].children[pageIndex].dataSources = [];
4392
4245
  }
4393
- const freeId = newConfig.children[0].children[pageIndex].dataSources.length + 1;
4394
- const parameters = eqlParametersToPayload(additional.parameters);
4395
- newConfig.children[0].children[pageIndex].dataSources.push({
4396
- name: `datasource_${freeId}`,
4397
- query,
4398
- parameters,
4399
- offset: 0,
4400
- limit: DEFAULT_DATA_SOURCE_LIMIT,
4246
+ else if (value) {
4247
+ result = result.replace(new RegExp(filterName), value);
4248
+ }
4249
+ return isSingle && isNumeric(result) ? Number(result) : result;
4250
+ };
4251
+ const applyVarsToCondition = ({ section, configFilters, filters, attributes, layerParams, eqlParameters, geometry, isSetParams, }) => {
4252
+ if (!section?.length)
4253
+ return [];
4254
+ const isSingle = typeof section === "string";
4255
+ const result = isSingle ? [section] : [...section];
4256
+ result.forEach((_, index) => {
4257
+ if (geometry && !geometry.includes("()") && geometry.endsWith(")")) {
4258
+ result[index] = result[index].replace(new RegExp("%geometry"), `'${geometry}'`);
4259
+ }
4260
+ if (configFilters?.length) {
4261
+ configFilters.forEach(filter => {
4262
+ result[index] = applyFiltersToCondition({
4263
+ filters,
4264
+ condition: result[index],
4265
+ name: filter.name,
4266
+ defaultValue: filter.defaultValue,
4267
+ isSetParams,
4268
+ isSingle: isSingle && !Array.isArray(filter.defaultValue),
4269
+ }).toString();
4270
+ });
4271
+ }
4272
+ if (attributes?.length) {
4273
+ attributes.forEach(({ attributeName, value, type: attributeType }) => {
4274
+ result[index] = result[index].replace(new RegExp(`{${attributeName}}`), formatConditionValue({ value: value?.toString(), attributeType, checkQuotes: !isSingle }));
4275
+ });
4276
+ }
4277
+ if (eqlParameters && layerParams) {
4278
+ Object.keys(eqlParameters).forEach(param => {
4279
+ result[index] = result[index].replace(new RegExp(`\\$\\(${param}=`), "***");
4280
+ result[index] = result[index].replace(new RegExp(param), formatConditionValue({ value: layerParams[param], checkQuotes: !isSingle }));
4281
+ result[index] = result[index].replace(new RegExp("\\*\\*\\*"), `$(${param}=`);
4282
+ });
4283
+ }
4401
4284
  });
4402
- return newConfig.children;
4285
+ return isSingle ? result?.[0] : result;
4403
4286
  };
4404
-
4405
- const addDataSources = (dashboardConfiguration, pageIndex, layerNames) => {
4406
- const newConfig = JSON.parse(JSON.stringify(dashboardConfiguration));
4407
- if (!newConfig.children[0].children[pageIndex].dataSources) {
4408
- newConfig.children[0].children[pageIndex].dataSources = [];
4409
- }
4410
- layerNames.forEach(layerName => {
4411
- newConfig.children[0].children[pageIndex].dataSources.push({
4412
- name: layerName.split(".")[1],
4413
- layerName,
4414
- query: "",
4415
- parameters: {},
4416
- });
4287
+ const formatSingleCondition = (condition, { configFilters, filters, attributes, eqlParameters, layerParams, geometry }) => {
4288
+ const setParams = condition.match(new RegExp("\\$\\([^)]+\\)", "g"));
4289
+ const setParamsSection = applyVarsToCondition({
4290
+ section: setParams,
4291
+ configFilters,
4292
+ filters,
4293
+ attributes,
4294
+ eqlParameters,
4295
+ layerParams,
4296
+ geometry,
4297
+ isSetParams: true,
4417
4298
  });
4418
- return newConfig.children;
4299
+ const splitter = " AND ";
4300
+ const setParamsLength = setParams?.join("").length || 0;
4301
+ const conditionSection = applyVarsToCondition({
4302
+ section: condition.substring(setParamsLength).split(splitter),
4303
+ configFilters,
4304
+ filters,
4305
+ attributes,
4306
+ eqlParameters,
4307
+ layerParams,
4308
+ geometry,
4309
+ });
4310
+ return setParamsSection?.length && conditionSection.length
4311
+ ? [setParamsSection.join(""), conditionSection.join(splitter)].join(" ")
4312
+ : setParamsSection?.length
4313
+ ? setParamsSection.join("")
4314
+ : conditionSection.join(splitter);
4315
+ };
4316
+ const formatDataSourceCondition = ({ condition, ...rest }) => {
4317
+ if (Array.isArray(condition)) {
4318
+ return condition.map(item => formatSingleCondition(item, rest));
4319
+ }
4320
+ if (!condition) {
4321
+ return "";
4322
+ }
4323
+ return formatSingleCondition(condition, rest);
4419
4324
  };
4420
4325
 
4421
- const getConfigFilter = (filterName, configFilters) => configFilters?.find(({ name }) => name === filterName);
4326
+ const DashboardChipsContainer = styled(uilibGl.Flex) `
4327
+ flex-wrap: wrap;
4328
+ `;
4329
+ const DefaultChipColorMixin = styled.css `
4330
+ && {
4331
+ color: ${({ theme: { palette } }) => palette.textPrimary};
4332
+ }
4422
4333
 
4423
- const getDataSource = (dataSourceName, dataSources) => dataSources?.find(({ name }) => name === dataSourceName);
4334
+ && > * {
4335
+ color: ${({ theme: { palette } }) => palette.textPrimary};
4336
+ }
4424
4337
 
4425
- const getDataSourceFilterValue = ({ filterName, filterProp, attributeAlias, dataSource, selectedFilters, }) => {
4426
- if (lodash.isNil(selectedFilters[filterName]))
4427
- return null;
4428
- const feature = dataSource?.features?.find(({ properties }) => properties[attributeAlias] ===
4429
- (Array.isArray(selectedFilters[filterName].value)
4430
- ? selectedFilters[filterName].value[0]
4431
- : selectedFilters[filterName].value));
4432
- return feature?.properties?.[filterProp];
4433
- };
4338
+ && span[kind]:after {
4339
+ color: ${({ theme: { palette } }) => palette.icon};
4340
+ }
4341
+ `;
4342
+ const CustomChipColorMixin = styled.css `
4343
+ && {
4344
+ color: ${({ $fontColor }) => $fontColor};
4345
+ }
4434
4346
 
4435
- const getSelectedFilterValue = (filterName, selectedFilters, defaultValue) => {
4436
- return ((!lodash.isNil(selectedFilters?.[filterName]?.value) &&
4437
- Array.isArray(defaultValue) &&
4438
- !Array.isArray(selectedFilters[filterName].value)
4439
- ? [selectedFilters[filterName].value]
4440
- : selectedFilters?.[filterName]?.value) ?? defaultValue);
4441
- };
4347
+ && > * {
4348
+ color: ${({ $fontColor }) => $fontColor};
4349
+ }
4442
4350
 
4443
- const applyQueryFilters = ({ parameters: configParameters, filters: configFilters, selectedFilters, geometry, attributes, layerInfo, dataSources, projectDataSources, }) => {
4444
- if (!configParameters) {
4445
- return {};
4446
- }
4447
- return Object.keys(configParameters).reduce((result, key) => {
4448
- if (typeof configParameters[key] === "string" && configParameters[key].startsWith(PROVIDER_PREFIX)) {
4449
- const raw = configParameters[key].slice(PROVIDER_PREFIX.length);
4450
- const [provider, layerName, fieldName] = raw.split(":");
4451
- if (provider === exports.ProviderPrefix.Card && fieldName && layerInfo.name === layerName && attributes) {
4452
- const attribute = attributes.find(({ attributeName }) => attributeName === fieldName);
4453
- const cardValue = attribute?.value ?? null;
4454
- if (lodash.isNil(cardValue))
4455
- return result;
4456
- return { ...result, [key]: cardValue };
4457
- }
4458
- if (provider === exports.ProviderPrefix.Left && layerName && fieldName && projectDataSources?.length) {
4459
- const dataSource = projectDataSources.find(({ layerName: name }) => name === layerName);
4460
- const leftValue = dataSource?.features?.[0]?.properties?.[fieldName] ?? null;
4461
- if (lodash.isNil(leftValue))
4462
- return result;
4463
- return { ...result, [key]: leftValue };
4464
- }
4465
- return result;
4466
- }
4467
- if (typeof configParameters[key] === "string" && configParameters[key].includes("{") && attributes?.length) {
4468
- const exactAttr = attributes.find(({ attributeName }) => configParameters[key] === `{${attributeName}}`);
4469
- if (exactAttr) {
4470
- return { ...result, [key]: exactAttr.value ?? "" };
4471
- }
4472
- let interpolated = configParameters[key];
4473
- attributes.forEach(({ attributeName, value: attrValue }) => {
4474
- interpolated = interpolated.replace(new RegExp(`\\{${attributeName}\\}`, "g"), attrValue?.toString() ?? "");
4475
- });
4476
- return { ...result, [key]: interpolated };
4477
- }
4478
- if (typeof configParameters[key] !== "string" || !configParameters[key].startsWith("%")) {
4479
- return {
4480
- ...result,
4481
- [key]: configParameters[key],
4482
- };
4483
- }
4484
- const filterFullName = configParameters[key].replace("%", "");
4485
- const [filterName, filterProp] = filterFullName.includes(".") ? filterFullName.split(".") : [filterFullName, null];
4486
- const { defaultValue, relatedDataSource, attributeAlias } = getConfigFilter(filterName, configFilters) || {};
4487
- if (filterName === "geometry" && geometry && !geometry.includes("()") && geometry.endsWith(")")) {
4488
- return {
4489
- ...result,
4490
- [key]: geometry,
4491
- };
4492
- }
4493
- if (configParameters[key].endsWith(".max")) {
4494
- return {
4495
- ...result,
4496
- [key]: selectedFilters?.[filterName]?.max ?? (Array.isArray(defaultValue) ? defaultValue[1] : defaultValue),
4497
- };
4498
- }
4499
- if (configParameters[key].endsWith(".min")) {
4500
- return {
4501
- ...result,
4502
- [key]: selectedFilters?.[filterName]?.min ?? (Array.isArray(defaultValue) ? defaultValue[0] : defaultValue),
4503
- };
4504
- }
4505
- if (configParameters[key].includes(".")) {
4506
- return {
4507
- ...result,
4508
- [key]: getDataSourceFilterValue({
4509
- filterName,
4510
- filterProp,
4511
- selectedFilters,
4512
- attributeAlias,
4513
- dataSource: getDataSource(relatedDataSource, dataSources),
4514
- }) ?? defaultValue,
4515
- };
4516
- }
4517
- const value = getSelectedFilterValue(filterName, selectedFilters, defaultValue);
4518
- if (lodash.isNil(value)) {
4519
- return result;
4520
- }
4521
- return {
4522
- ...result,
4523
- [key]: value,
4524
- };
4525
- }, {});
4526
- };
4351
+ && span[kind]:after {
4352
+ color: ${({ $fontColor }) => $fontColor};
4353
+ }
4354
+ `;
4355
+ const DashboardChip$1 = styled(uilibGl.Chip) `
4356
+ margin: 0 0.25rem 0.25rem 0;
4357
+ background: ${({ $isDefault, $bgColor, theme: { palette } }) => $isDefault ? palette.element : $bgColor || palette.primary};
4358
+ border-radius: ${({ $radius, theme: { borderRadius } }) => $radius || borderRadius.medium};
4359
+ white-space: nowrap;
4360
+ font-size: ${({ $fontSize }) => $fontSize || "0.875rem"};
4361
+ color: ${({ theme: { palette } }) => palette.iconContrast};
4527
4362
 
4528
- const checkEqualOrIncludes = (arrayOrSingle, value) => arrayOrSingle && Array.isArray(arrayOrSingle) ? arrayOrSingle.includes(value) : arrayOrSingle === value;
4363
+ > * {
4364
+ font-size: ${({ $fontSize }) => $fontSize || "0.875rem"};
4365
+ }
4529
4366
 
4530
- const checkIsLoading = (dataSources, config, filters) => config?.children?.some(child => {
4531
- const filter = filters?.find(({ name }) => name === child.options?.filterName);
4532
- return (!!filter && !!child.options?.filterName && !dataSources?.some(({ name }) => name === filter.relatedDataSource));
4533
- });
4367
+ span[kind] {
4368
+ height: 0.875rem;
4534
4369
 
4535
- const createConfigLayer = (layerName) => ({
4536
- name: layerName,
4537
- query: "",
4538
- parameters: {},
4539
- opacity: 1,
4540
- selectable: false,
4541
- filterZoomTo: false,
4542
- isVisible: true,
4543
- });
4370
+ :after {
4371
+ font-size: 0.875rem;
4372
+ }
4373
+ }
4544
4374
 
4545
- const createNewPageId = (pages) => {
4546
- if (!pages?.length)
4547
- return 1;
4548
- const maxPageId = pages.reduce((maxId, page) => {
4549
- const pageId = parseInt(page.id.replace(CONFIG_PAGE_ID, ""), 10);
4550
- return Math.max(maxId, pageId);
4551
- }, 0);
4552
- return maxPageId + 1;
4553
- };
4375
+ button {
4376
+ width: auto;
4377
+ padding: 0 0.5rem;
4378
+ }
4554
4379
 
4555
- const createConfigPage = (props) => {
4556
- const { title, defaultTitle, pages, baseMapName, position, resolution /* , srid*/ } = props || {};
4557
- const pageId = createNewPageId(pages);
4558
- return {
4559
- id: `${CONFIG_PAGE_ID}${pageId}`,
4560
- templateName: exports.ContainerTemplate.ContainersGroup,
4561
- children: [],
4562
- layers: [],
4563
- dataSources: [],
4564
- filters: [],
4565
- tasks: [],
4566
- style: {},
4567
- options: {
4568
- title: title || [defaultTitle, pageId].filter(Boolean).join(" "),
4569
- position: position || [DEFAULT_LNG, DEFAULT_LAT],
4570
- resolution: resolution || DEFAULT_ZOOM,
4571
- baseMapName: baseMapName || DEFAULT_BASE_MAP,
4572
- baseMapSettings: {},
4573
- expandedLayers: true,
4574
- // srid: srid || null,
4575
- },
4576
- };
4577
- };
4380
+ ${({ $isDefault }) => $isDefault && DefaultChipColorMixin}
4381
+ ${({ $fontColor, $isDefault }) => !!$fontColor && !$isDefault && CustomChipColorMixin}
4382
+ `;
4578
4383
 
4579
- const getAttributesConfiguration = (layer) => {
4580
- const layerAttributeConfiguration = layer?.configuration?.attributesConfiguration ?? {};
4581
- const { geometryAttribute, idAttribute } = layerAttributeConfiguration;
4582
- const emptyLayerDefinition = {
4583
- attributes: [],
4584
- geometryAttribute: geometryAttribute ?? GEOMETRY_ATTRIBUTE,
4585
- idAttribute: idAttribute ?? DEFAULT_ID_ATTRIBUTE_NAME,
4586
- titleAttribute: "",
4587
- };
4588
- if (!isLayerService(layer)) {
4589
- return emptyLayerDefinition;
4590
- }
4591
- return {
4592
- ...emptyLayerDefinition,
4593
- ...layerAttributeConfiguration,
4594
- attributes: layerAttributeConfiguration?.attributes ||
4595
- emptyLayerDefinition.attributes,
4596
- };
4597
- };
4384
+ const LayerGroupContainer = styled(uilibGl.Flex) `
4385
+ display: flex;
4386
+ justify-content: center;
4387
+ position: relative;
4388
+ flex-direction: column;
4389
+ padding: 0 0.25rem 0 1rem;
4390
+ box-sizing: border-box;
4391
+ transition: opacity ${uilibGl.transition.hover}, background-color ${uilibGl.transition.hover};
4392
+ font-family: "NunitoSans", sans-serif;
4393
+ `;
4394
+ const LayerGroupMain = styled(uilibGl.Flex) `
4395
+ flex-direction: row;
4396
+ flex-wrap: nowrap;
4397
+ align-items: center;
4398
+ justify-content: space-between;
4399
+ width: 100%;
4598
4400
 
4599
- const formatChartRelatedValue = (t, value, layerInfo, relatedAttributes) => {
4600
- const layerDefinition = getAttributesConfiguration(layerInfo);
4601
- const relatedAxis = relatedAttributes.find(({ chartAxis }) => chartAxis === "y");
4602
- const attribute = layerDefinition.attributes[relatedAxis.attributeName];
4603
- return attribute ? formatAttributeValue({ t, type: attribute.type, value, stringFormat: attribute.stringFormat }) : value;
4604
- };
4401
+ ${uilibGl.Icon} {
4402
+ width: 2rem;
4403
+ min-width: 2rem;
4404
+ height: 2rem;
4405
+ display: inline-flex;
4406
+ align-items: center;
4407
+ justify-content: center;
4408
+ margin-right: 0.75rem;
4409
+ }
4605
4410
 
4606
- const isEmptyValue = (value) => value === "" || value === null || value === undefined;
4607
-
4608
- const checkIsDateFormat = (value) => value.startsWith("#'") && value.endsWith("'");
4609
- const checkIfNeedQuotes = (value, attributeType) => {
4610
- return attributeType
4611
- ? !NUMERIC_ATTRIBUTE_TYPES.includes(attributeType)
4612
- : typeof value === "number"
4613
- ? false
4614
- : typeof value === "string" && !isNumeric(value) && !checkIsDateFormat(value);
4615
- };
4616
- const formatConditionValue = ({ value, attributeType, defaultValue, checkQuotes = true, isSetParams = false, }) => {
4617
- if (isEmptyValue(value))
4618
- return "";
4619
- const formattedValue = Array.isArray(defaultValue) && !Array.isArray(value) ? [value] : value;
4620
- const isArray = Array.isArray(formattedValue);
4621
- let stringValue = formattedValue.toString();
4622
- if (isArray) {
4623
- return formattedValue.length && !formattedValue.every(item => !item)
4624
- ? `[${formattedValue
4625
- .map(item => (checkQuotes && checkIfNeedQuotes(item, attributeType) ? `'${item}'` : item))
4626
- .join(",")}]`
4627
- : "";
4628
- }
4629
- if (isSetParams && checkIsDateFormat(stringValue)) {
4630
- stringValue = stringValue.slice(2, -1);
4631
- }
4632
- return checkQuotes && checkIfNeedQuotes(stringValue, attributeType) ? `'${stringValue}'` : stringValue;
4633
- };
4634
-
4635
- const applyFiltersToCondition = ({ condition, name, defaultValue, filters, isSingle, isSetParams, }) => {
4636
- const hasFilter = filters[name] !== undefined && !!filters[name].value?.toString().length;
4637
- const min = formatConditionValue({
4638
- value: hasFilter ? filters[name].min : null,
4639
- checkQuotes: !isSingle,
4640
- isSetParams,
4641
- });
4642
- const max = formatConditionValue({
4643
- value: hasFilter ? filters[name].max : null,
4644
- checkQuotes: !isSingle,
4645
- isSetParams,
4646
- });
4647
- const value = formatConditionValue({
4648
- value: hasFilter ? filters[name].value : filters[name] === undefined ? defaultValue : "",
4649
- defaultValue,
4650
- checkQuotes: !isSingle,
4651
- isSetParams,
4652
- });
4653
- const filterName = `${FILTER_PREFIX}${name}`;
4654
- const hasMin = min !== "" && min !== null && min !== undefined;
4655
- const hasMax = max !== "" && max !== null && max !== undefined;
4656
- let result = condition;
4657
- if (hasMin || hasMax) {
4658
- if (hasMin) {
4659
- result = result.replace(new RegExp(`${filterName}\\.min`), min);
4660
- }
4661
- if (hasMax) {
4662
- result = result.replace(new RegExp(`${filterName}\\.max`), max);
4663
- }
4664
- }
4665
- else if (value) {
4666
- result = result.replace(new RegExp(filterName), value);
4667
- }
4668
- return isSingle && isNumeric(result) ? Number(result) : result;
4669
- };
4670
- const applyVarsToCondition = ({ section, configFilters, filters, attributes, layerParams, eqlParameters, geometry, isSetParams, }) => {
4671
- if (!section?.length)
4672
- return [];
4673
- const isSingle = typeof section === "string";
4674
- const result = isSingle ? [section] : [...section];
4675
- result.forEach((_, index) => {
4676
- if (geometry && !geometry.includes("()") && geometry.endsWith(")")) {
4677
- result[index] = result[index].replace(new RegExp("%geometry"), `'${geometry}'`);
4678
- }
4679
- if (configFilters?.length) {
4680
- configFilters.forEach(filter => {
4681
- result[index] = applyFiltersToCondition({
4682
- filters,
4683
- condition: result[index],
4684
- name: filter.name,
4685
- defaultValue: filter.defaultValue,
4686
- isSetParams,
4687
- isSingle: isSingle && !Array.isArray(filter.defaultValue),
4688
- }).toString();
4689
- });
4690
- }
4691
- if (attributes?.length) {
4692
- attributes.forEach(({ attributeName, value, type: attributeType }) => {
4693
- result[index] = result[index].replace(new RegExp(`{${attributeName}}`), formatConditionValue({ value: value?.toString(), attributeType, checkQuotes: !isSingle }));
4694
- });
4695
- }
4696
- if (eqlParameters && layerParams) {
4697
- Object.keys(eqlParameters).forEach(param => {
4698
- result[index] = result[index].replace(new RegExp(`\\$\\(${param}=`), "***");
4699
- result[index] = result[index].replace(new RegExp(param), formatConditionValue({ value: layerParams[param], checkQuotes: !isSingle }));
4700
- result[index] = result[index].replace(new RegExp("\\*\\*\\*"), `$(${param}=`);
4701
- });
4702
- }
4703
- });
4704
- return isSingle ? result?.[0] : result;
4705
- };
4706
- const formatSingleCondition = (condition, { configFilters, filters, attributes, eqlParameters, layerParams, geometry }) => {
4707
- const setParams = condition.match(new RegExp("\\$\\([^)]+\\)", "g"));
4708
- const setParamsSection = applyVarsToCondition({
4709
- section: setParams,
4710
- configFilters,
4711
- filters,
4712
- attributes,
4713
- eqlParameters,
4714
- layerParams,
4715
- geometry,
4716
- isSetParams: true,
4717
- });
4718
- const splitter = " AND ";
4719
- const setParamsLength = setParams?.join("").length || 0;
4720
- const conditionSection = applyVarsToCondition({
4721
- section: condition.substring(setParamsLength).split(splitter),
4722
- configFilters,
4723
- filters,
4724
- attributes,
4725
- eqlParameters,
4726
- layerParams,
4727
- geometry,
4728
- });
4729
- return setParamsSection?.length && conditionSection.length
4730
- ? [setParamsSection.join(""), conditionSection.join(splitter)].join(" ")
4731
- : setParamsSection?.length
4732
- ? setParamsSection.join("")
4733
- : conditionSection.join(splitter);
4734
- };
4735
- const formatDataSourceCondition = ({ condition, ...rest }) => {
4736
- if (Array.isArray(condition)) {
4737
- return condition.map(item => formatSingleCondition(item, rest));
4738
- }
4739
- if (!condition) {
4740
- return "";
4741
- }
4742
- return formatSingleCondition(condition, rest);
4743
- };
4744
-
4745
- const DashboardChipsContainer = styled(uilibGl.Flex) `
4746
- flex-wrap: wrap;
4747
- `;
4748
- const DefaultChipColorMixin = styled.css `
4749
- && {
4750
- color: ${({ theme: { palette } }) => palette.textPrimary};
4751
- }
4752
-
4753
- && > * {
4754
- color: ${({ theme: { palette } }) => palette.textPrimary};
4755
- }
4756
-
4757
- && span[kind]:after {
4758
- color: ${({ theme: { palette } }) => palette.icon};
4759
- }
4760
- `;
4761
- const CustomChipColorMixin = styled.css `
4762
- && {
4763
- color: ${({ $fontColor }) => $fontColor};
4764
- }
4765
-
4766
- && > * {
4767
- color: ${({ $fontColor }) => $fontColor};
4768
- }
4769
-
4770
- && span[kind]:after {
4771
- color: ${({ $fontColor }) => $fontColor};
4772
- }
4773
- `;
4774
- const DashboardChip$1 = styled(uilibGl.Chip) `
4775
- margin: 0 0.25rem 0.25rem 0;
4776
- background: ${({ $isDefault, $bgColor, theme: { palette } }) => $isDefault ? palette.element : $bgColor || palette.primary};
4777
- border-radius: ${({ $radius, theme: { borderRadius } }) => $radius || borderRadius.medium};
4778
- white-space: nowrap;
4779
- font-size: ${({ $fontSize }) => $fontSize || "0.875rem"};
4780
- color: ${({ theme: { palette } }) => palette.iconContrast};
4781
-
4782
- > * {
4783
- font-size: ${({ $fontSize }) => $fontSize || "0.875rem"};
4784
- }
4785
-
4786
- span[kind] {
4787
- height: 0.875rem;
4788
-
4789
- :after {
4790
- font-size: 0.875rem;
4791
- }
4792
- }
4793
-
4794
- button {
4795
- width: auto;
4796
- padding: 0 0.5rem;
4797
- }
4798
-
4799
- ${({ $isDefault }) => $isDefault && DefaultChipColorMixin}
4800
- ${({ $fontColor, $isDefault }) => !!$fontColor && !$isDefault && CustomChipColorMixin}
4801
- `;
4802
-
4803
- const LayerGroupContainer = styled(uilibGl.Flex) `
4804
- display: flex;
4805
- justify-content: center;
4806
- position: relative;
4807
- flex-direction: column;
4808
- padding: 0 0.25rem 0 1rem;
4809
- box-sizing: border-box;
4810
- transition: opacity ${uilibGl.transition.hover}, background-color ${uilibGl.transition.hover};
4811
- font-family: "NunitoSans", sans-serif;
4812
- `;
4813
- const LayerGroupMain = styled(uilibGl.Flex) `
4814
- flex-direction: row;
4815
- flex-wrap: nowrap;
4816
- align-items: center;
4817
- justify-content: space-between;
4818
- width: 100%;
4819
-
4820
- ${uilibGl.Icon} {
4821
- width: 2rem;
4822
- min-width: 2rem;
4823
- height: 2rem;
4824
- display: inline-flex;
4825
- align-items: center;
4826
- justify-content: center;
4827
- margin-right: 0.75rem;
4828
- }
4829
-
4830
- ${uilibGl.Description} {
4831
- display: flex;
4832
- align-items: center;
4833
- flex-grow: 1;
4834
- width: 100%;
4835
- margin-right: 0.25rem;
4836
- color: ${({ theme }) => theme.palette.textPrimary};
4837
- }
4411
+ ${uilibGl.Description} {
4412
+ display: flex;
4413
+ align-items: center;
4414
+ flex-grow: 1;
4415
+ width: 100%;
4416
+ margin-right: 0.25rem;
4417
+ color: ${({ theme }) => theme.palette.textPrimary};
4418
+ }
4838
4419
 
4839
4420
  button {
4840
4421
  width: 2.25rem;
@@ -4971,41 +4552,193 @@ const customStyles = [
4971
4552
  },
4972
4553
  ];
4973
4554
 
4974
- const useMapContext = () => {
4975
- return React.useContext(MapContext);
4976
- };
4555
+ const DashboardContext = React.createContext({});
4556
+ const DashboardProvider = React.memo(({ children, ...props }) => {
4557
+ return jsxRuntime.jsx(DashboardContext.Provider, { value: props, children: children });
4558
+ });
4977
4559
 
4978
- const draw = new MapboxDraw({
4979
- displayControlsDefault: false,
4980
- styles: customStyles,
4981
- modes: customModes,
4982
- defaultMode: "static",
4983
- controls: {
4984
- trash: true,
4985
- },
4560
+ const FeatureCardContext = React.createContext({});
4561
+ const FeatureCardProvider = React.memo(({ children, ...props }) => {
4562
+ return jsxRuntime.jsx(FeatureCardContext.Provider, { value: props, children: children });
4986
4563
  });
4987
- const useMapDraw = (triggerDeps = []) => {
4988
- const { map, draw: drawContext, loaded, basemapName } = useMapContext();
4989
- React.useEffect(() => {
4990
- if (!loaded || !map.current) {
4991
- return;
4992
- }
4993
- drawContext.current = draw;
4994
- map.current.addControl(drawContext.current);
4995
- }, [loaded]); // eslint-disable-line
4996
- React.useEffect(() => {
4997
- if (map.current && map.current.hasControl(drawContext.current)) {
4998
- map.current.removeControl(drawContext.current);
4999
- map.current.addControl(drawContext.current);
5000
- }
5001
- }, [basemapName, ...triggerDeps]); // eslint-disable-line
5002
- };
5003
4564
 
5004
- /**
5005
- * Определяет, является ли URL ссылкой на SVG файл.
5006
- */
5007
- const isSvgUrl = (url) => {
5008
- const lowercaseUrl = url.toLowerCase();
4565
+ const GlobalContext = React.createContext({});
4566
+ const GlobalProvider = React.memo(({ children, ...props }) => {
4567
+ return jsxRuntime.jsx(GlobalContext.Provider, { value: props, children: children });
4568
+ });
4569
+
4570
+ const MapContext = React.createContext({});
4571
+
4572
+ const MapProvider = ({ basemapItems, defaultBasemap, children }) => {
4573
+ const map = React.useRef();
4574
+ const draw = React.useRef();
4575
+ const [loaded, setLoaded] = React.useState(false);
4576
+ const [basemapName, setBasemapName] = React.useState(defaultBasemap);
4577
+ return (jsxRuntime.jsx(MapContext.Provider, { value: {
4578
+ map,
4579
+ draw,
4580
+ loaded,
4581
+ setLoaded,
4582
+ basemapItems,
4583
+ basemapName,
4584
+ setBasemapName,
4585
+ defaultBasemap,
4586
+ }, children: children }));
4587
+ };
4588
+
4589
+ exports.BaseMapTheme = void 0;
4590
+ (function (BaseMapTheme) {
4591
+ BaseMapTheme["Light"] = "light";
4592
+ BaseMapTheme["Dark"] = "dark";
4593
+ })(exports.BaseMapTheme || (exports.BaseMapTheme = {}));
4594
+
4595
+ const ServerNotificationsContext = React.createContext({});
4596
+
4597
+ const useServerNotifications = (url, initialized, apiClient) => {
4598
+ const hubConnection = React.useRef(null);
4599
+ const [connection, setConnection] = React.useState(null);
4600
+ const subscribeNotifications = React.useCallback(() => {
4601
+ if (!connection || connection.state !== "Connected") {
4602
+ return;
4603
+ }
4604
+ connection
4605
+ .invoke("SubscribeNotifications", [])
4606
+ .then(() => console.info("Подписка `SubscribeNotifications` оформлена"))
4607
+ .catch(err => console.info("Ошибка подписки `SubscribeNotifications`:", err));
4608
+ }, [connection]);
4609
+ React.useEffect(() => {
4610
+ if (!initialized) {
4611
+ return;
4612
+ }
4613
+ hubConnection.current = new signalr.HubConnectionBuilder()
4614
+ .withUrl(`${url}?clientId=${api.generateId()}`, {
4615
+ withCredentials: true,
4616
+ skipNegotiation: true,
4617
+ transport: signalr.HttpTransportType.WebSockets,
4618
+ accessTokenFactory: async () => {
4619
+ let accessToken = window.localStorage.getItem(api.STORAGE_TOKEN_KEY) || "";
4620
+ const { exp } = api.parseJwt(accessToken);
4621
+ const currentTime = new Date().getTime() / 1000;
4622
+ if (currentTime > exp) {
4623
+ const refreshToken = window.localStorage.getItem(api.STORAGE_REFRESH_TOKEN_KEY);
4624
+ if (refreshToken) {
4625
+ const refreshTokenResponse = await apiClient.account.refreshToken({
4626
+ refreshToken,
4627
+ });
4628
+ if (refreshTokenResponse) {
4629
+ accessToken = refreshTokenResponse.token;
4630
+ window.localStorage.setItem(api.STORAGE_TOKEN_KEY, refreshTokenResponse.token);
4631
+ window.localStorage.setItem(api.STORAGE_REFRESH_TOKEN_KEY, refreshTokenResponse.refreshToken);
4632
+ }
4633
+ }
4634
+ else {
4635
+ await apiClient.logout();
4636
+ }
4637
+ }
4638
+ return accessToken;
4639
+ },
4640
+ })
4641
+ .withAutomaticReconnect()
4642
+ .configureLogging(signalr.LogLevel.Information)
4643
+ .build();
4644
+ hubConnection.current
4645
+ .start()
4646
+ .then(() => console.info("Серверные нотификации подключены"))
4647
+ .catch(err => console.info("Ошибка:", err))
4648
+ .finally(() => setConnection(hubConnection.current));
4649
+ }, [initialized]); // eslint-disable-line
4650
+ React.useEffect(() => {
4651
+ if (!connection || connection.state !== "Connected") {
4652
+ return;
4653
+ }
4654
+ connection.onreconnecting(() => console.info("Переподключение к серверным нотификациям"));
4655
+ connection.onreconnected(subscribeNotifications);
4656
+ subscribeNotifications();
4657
+ }, [connection]);
4658
+ return connection;
4659
+ };
4660
+
4661
+ const ServerNotificationsProvider = ({ url, initialized, apiClient, children }) => {
4662
+ const connection = useServerNotifications(url, initialized, apiClient);
4663
+ const addSubscription = React.useCallback(async (payload) => {
4664
+ if (!connection || connection.state !== "Connected" || !payload) {
4665
+ return;
4666
+ }
4667
+ try {
4668
+ const id = await connection.invoke("AddSubscription", payload);
4669
+ console.info("Подписка добавлена, id:", id);
4670
+ return id;
4671
+ }
4672
+ catch (err) {
4673
+ console.info("Ошибка добавления подписки:", err);
4674
+ return Promise.resolve(null);
4675
+ }
4676
+ }, [connection]);
4677
+ const updateSubscription = React.useCallback(async (id, payload) => {
4678
+ if (!connection || connection.state !== "Connected" || !id || !payload) {
4679
+ return;
4680
+ }
4681
+ try {
4682
+ await connection.invoke("UpdateSubscription", id, payload);
4683
+ }
4684
+ catch (err) {
4685
+ console.info(`Ошибка обновления подписки ${id}:`, err);
4686
+ }
4687
+ }, [connection]);
4688
+ const unsubscribeById = React.useCallback(async (id) => {
4689
+ if (!connection || connection.state !== "Connected" || !id) {
4690
+ return;
4691
+ }
4692
+ try {
4693
+ await connection.invoke("Unsubscribe", [id]);
4694
+ }
4695
+ catch (err) {
4696
+ console.info(`Ошибка отписки по ${id}:`, err);
4697
+ }
4698
+ }, [connection]);
4699
+ return (jsxRuntime.jsx(ServerNotificationsContext.Provider, { value: {
4700
+ connection,
4701
+ addSubscription,
4702
+ updateSubscription,
4703
+ unsubscribeById,
4704
+ }, children: children }));
4705
+ };
4706
+
4707
+ const useMapContext = () => {
4708
+ return React.useContext(MapContext);
4709
+ };
4710
+
4711
+ const draw = new MapboxDraw({
4712
+ displayControlsDefault: false,
4713
+ styles: customStyles,
4714
+ modes: customModes,
4715
+ defaultMode: "static",
4716
+ controls: {
4717
+ trash: true,
4718
+ },
4719
+ });
4720
+ const useMapDraw = (triggerDeps = []) => {
4721
+ const { map, draw: drawContext, loaded, basemapName } = useMapContext();
4722
+ React.useEffect(() => {
4723
+ if (!loaded || !map.current) {
4724
+ return;
4725
+ }
4726
+ drawContext.current = draw;
4727
+ map.current.addControl(drawContext.current);
4728
+ }, [loaded]); // eslint-disable-line
4729
+ React.useEffect(() => {
4730
+ if (map.current && map.current.hasControl(drawContext.current)) {
4731
+ map.current.removeControl(drawContext.current);
4732
+ map.current.addControl(drawContext.current);
4733
+ }
4734
+ }, [basemapName, ...triggerDeps]); // eslint-disable-line
4735
+ };
4736
+
4737
+ /**
4738
+ * Определяет, является ли URL ссылкой на SVG файл.
4739
+ */
4740
+ const isSvgUrl = (url) => {
4741
+ const lowercaseUrl = url.toLowerCase();
5009
4742
  return lowercaseUrl.endsWith(".svg") || lowercaseUrl.startsWith("data:image/svg");
5010
4743
  };
5011
4744
  /**
@@ -5385,6 +5118,7 @@ const useZoomToFeatures = () => {
5385
5118
  map.current.fitBounds([minX, minY, maxX, maxY], {
5386
5119
  ...options,
5387
5120
  padding: options?.padding ?? 150,
5121
+ maxZoom: options?.maxZoom ?? SINGLE_FEATURE_FALLBACK_ZOOM,
5388
5122
  });
5389
5123
  }, [map]);
5390
5124
  };
@@ -6338,71 +6072,12 @@ const TwoColumnContainerWrapper = styled(uilibGl.Flex) `
6338
6072
  }
6339
6073
  `;
6340
6074
 
6341
- const useRenderContainerItem = (type, renderElement) => {
6342
- const { config, layerInfo, selectedTabId, attributes } = useWidgetContext(type);
6343
- return React.useCallback((elementConfig, attribute) => {
6344
- const { id, options, style, children } = elementConfig || {};
6345
- const { hideEmpty, innerTemplateStyle } = options || {};
6346
- const hasUnits = children?.some(item => item.id === "units");
6347
- const iconIndex = children?.findIndex(item => item.id === "icon");
6348
- const icon = children?.[iconIndex];
6349
- const hasIcon = !!icon;
6350
- const elementChildren = elementConfig?.children?.map(child => ({
6351
- type: "attributeValue",
6352
- ...child,
6353
- attributeName: attribute,
6354
- options: { noUnits: hasUnits, ...child.options },
6355
- }));
6356
- const attr = attribute
6357
- ? layerInfo?.configuration?.attributesConfiguration?.attributes?.find(({ attributeName }) => attributeName === attribute)
6358
- : null;
6359
- if (hasIcon) {
6360
- elementChildren[iconIndex] = {
6361
- ...elementChildren[iconIndex],
6362
- type: attr?.icon?.type?.toLowerCase(),
6363
- value: attr?.icon?.resourceId || attr?.icon?.url || attr?.icon?.iconName,
6364
- attributeName: null,
6365
- };
6366
- }
6367
- const render = attribute
6368
- ? getRenderElement({
6369
- config,
6370
- elementConfig: {
6371
- ...elementConfig,
6372
- children: elementChildren,
6373
- },
6374
- selectedTabId,
6375
- attributes,
6376
- layerInfo,
6377
- type,
6378
- })
6379
- : renderElement;
6380
- const value = render({ id: "value" });
6381
- return {
6382
- id,
6383
- value,
6384
- hideEmpty,
6385
- style: innerTemplateStyle || style,
6386
- hasIcon,
6387
- hasUnits,
6388
- render,
6389
- };
6390
- }, [config, layerInfo, selectedTabId, attributes, type, renderElement]);
6391
- };
6392
-
6393
6075
  const OneColumnContainer = React.memo(({ type, elementConfig, renderElement }) => {
6394
- const getRenderContainerItem = useRenderContainerItem(type, renderElement);
6395
- const { options } = elementConfig || {};
6396
- const { attributes: optionAttributes } = options || {};
6397
- const { attributes: contextAttributes } = useWidgetContext(type);
6398
- const attributesToRender = React.useMemo(() => {
6399
- if (!optionAttributes)
6400
- return null;
6401
- if (optionAttributes.length === 0) {
6402
- return contextAttributes?.map(({ attributeName }) => attributeName) ?? [];
6403
- }
6404
- return optionAttributes;
6405
- }, [optionAttributes, contextAttributes]);
6076
+ const { getRenderContainerItem, attributesToRender } = useContainerAttributes({
6077
+ elementConfig,
6078
+ type,
6079
+ renderElement,
6080
+ });
6406
6081
  const renderContainer = React.useCallback((attribute) => {
6407
6082
  const { id, value, hideEmpty, style, hasUnits, render } = getRenderContainerItem(elementConfig, attribute);
6408
6083
  if (!value && hideEmpty)
@@ -6413,18 +6088,11 @@ const OneColumnContainer = React.memo(({ type, elementConfig, renderElement }) =
6413
6088
  });
6414
6089
 
6415
6090
  const TwoColumnContainer = React.memo(({ elementConfig, type, renderElement }) => {
6416
- const getRenderContainerItem = useRenderContainerItem(type, renderElement);
6417
- const { options } = elementConfig || {};
6418
- const { attributes: renderAttributes } = options || {};
6419
- const { attributes: contextAttributes } = useWidgetContext(type);
6420
- const attributesToRender = React.useMemo(() => {
6421
- if (!renderAttributes)
6422
- return null;
6423
- if (renderAttributes.length === 0) {
6424
- return contextAttributes?.map(({ attributeName }) => attributeName) ?? [];
6425
- }
6426
- return renderAttributes;
6427
- }, [renderAttributes, contextAttributes]);
6091
+ const { getRenderContainerItem, attributesToRender } = useContainerAttributes({
6092
+ elementConfig,
6093
+ type,
6094
+ renderElement,
6095
+ });
6428
6096
  const renderContainer = React.useCallback((attribute) => {
6429
6097
  const { id, value, hideEmpty, style, hasIcon, hasUnits, render } = getRenderContainerItem(elementConfig, attribute);
6430
6098
  if (!value && hideEmpty)
@@ -7739,14 +7407,51 @@ const EditGroupContainer = React.memo(({ type, elementConfig, renderElement }) =
7739
7407
  return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: controls.map(({ targetAttributeName }) => renderContainer(targetAttributeName)) }));
7740
7408
  });
7741
7409
 
7742
- const useEditControl = (type, elementConfig) => {
7743
- const { attributes, controls, dataSources, changeControls } = useWidgetContext(type);
7744
- const { children, options } = elementConfig || {};
7745
- const { controls: controlsOption } = options || {};
7746
- const valueElement = React.useMemo(() => children.find(({ id }) => id === "value"), [children]);
7747
- const control = React.useMemo(() => controlsOption?.find(item => item.targetAttributeName === valueElement.attributeName), [controlsOption, valueElement.attributeName]);
7748
- const attributeName = (control?.targetAttributeName ?? valueElement?.attributeName);
7749
- const value = React.useMemo(() => {
7410
+ const useWidgetContext = (type = exports.WidgetType.Dashboard) => {
7411
+ const { toggleLayersVisibility, visibleLayers, projectInfo, updateProject, layerInfos, geometryFilter, dashboardLayers, setDashboardLayer, components: dashboardComponents, config: dashboardConfig, containerIds, pageIndex: projectPageIndex, selectedTabId: projectSelectedTabId, setSelectedTabId: setProjectSelectedTabId, dataSources: projectDataSources, loading: projectLoading, editMode: projectEditMode, filters: projectFilters, changeFilters: projectChangeFilters, expandContainer: projectExpandContainer, expandedContainers: projectExpandedContainers, nextPage: projectNextPage, prevPage: projectPrevPage, changePage: projectChangePage, } = React.useContext(DashboardContext) || {};
7412
+ const { layerInfo, attributes, feature, controls, changeControls, closeFeatureCard, config: featureConfig, pageIndex: featurePageIndex, selectedTabId: featureSelectedTabId, setSelectedTabId: setFeatureSelectedTabId, dataSources: featureDataSources, loading: featureLoading, editMode: featureEditMode, filters: featureFilters, changeFilters: featureChangeFilters, expandContainer: featureExpandContainer, expandedContainers: featureExpandedContainers, nextPage: featureNextPage, prevPage: featurePrevPage, changePage: featureChangePage, } = React.useContext(FeatureCardContext) || {};
7413
+ return {
7414
+ toggleLayersVisibility,
7415
+ visibleLayers,
7416
+ projectInfo,
7417
+ layerInfos,
7418
+ updateProject,
7419
+ dashboardLayers,
7420
+ setDashboardLayer,
7421
+ geometryFilter,
7422
+ layerInfo,
7423
+ attributes,
7424
+ feature,
7425
+ closeFeatureCard,
7426
+ containerIds,
7427
+ controls,
7428
+ changeControls,
7429
+ components: dashboardComponents,
7430
+ config: type === exports.WidgetType.Dashboard ? dashboardConfig : featureConfig,
7431
+ isEditing: type === exports.WidgetType.Dashboard ? projectEditMode : featureEditMode,
7432
+ isLoading: type === exports.WidgetType.Dashboard ? projectLoading : featureLoading,
7433
+ pageIndex: type === exports.WidgetType.Dashboard ? projectPageIndex || 1 : featurePageIndex || 1,
7434
+ filters: type === exports.WidgetType.Dashboard ? projectFilters : featureFilters,
7435
+ changeFilters: type === exports.WidgetType.Dashboard ? projectChangeFilters : featureChangeFilters,
7436
+ dataSources: type === exports.WidgetType.Dashboard ? projectDataSources : featureDataSources,
7437
+ expandContainer: type === exports.WidgetType.Dashboard ? projectExpandContainer : featureExpandContainer,
7438
+ expandedContainers: type === exports.WidgetType.Dashboard ? projectExpandedContainers : featureExpandedContainers,
7439
+ selectedTabId: type === exports.WidgetType.Dashboard ? projectSelectedTabId : featureSelectedTabId,
7440
+ setSelectedTabId: type === exports.WidgetType.Dashboard ? setProjectSelectedTabId : setFeatureSelectedTabId,
7441
+ nextPage: type === exports.WidgetType.Dashboard ? projectNextPage : featureNextPage,
7442
+ prevPage: type === exports.WidgetType.Dashboard ? projectPrevPage : featurePrevPage,
7443
+ changePage: type === exports.WidgetType.Dashboard ? projectChangePage : featureChangePage,
7444
+ };
7445
+ };
7446
+
7447
+ const useEditControl = (type, elementConfig) => {
7448
+ const { attributes, controls, dataSources, changeControls } = useWidgetContext(type);
7449
+ const { children, options } = elementConfig || {};
7450
+ const { controls: controlsOption } = options || {};
7451
+ const valueElement = React.useMemo(() => children.find(({ id }) => id === "value"), [children]);
7452
+ const control = React.useMemo(() => controlsOption?.find(item => item.targetAttributeName === valueElement.attributeName), [controlsOption, valueElement.attributeName]);
7453
+ const attributeName = (control?.targetAttributeName ?? valueElement?.attributeName);
7454
+ const value = React.useMemo(() => {
7750
7455
  const currentValue = controls[attributeName] === undefined
7751
7456
  ? attributes.find(({ attributeName: name }) => name === attributeName)?.value
7752
7457
  : controls[attributeName];
@@ -10773,6 +10478,307 @@ const tooltipValueFromRelatedFeatures = (t, value, relatedAttributes, layerInfo)
10773
10478
  return formatChartRelatedValue(t, value, layerInfo, relatedAttributes);
10774
10479
  };
10775
10480
 
10481
+ const useRenderContainerItem = (type, renderElement) => {
10482
+ const { config, layerInfo, selectedTabId, attributes } = useWidgetContext(type);
10483
+ return React.useCallback((elementConfig, attribute) => {
10484
+ const { id, options, style, children } = elementConfig || {};
10485
+ const { hideEmpty, innerTemplateStyle } = options || {};
10486
+ const hasUnits = children?.some(item => item.id === "units");
10487
+ const iconIndex = children?.findIndex(item => item.id === "icon");
10488
+ const icon = children?.[iconIndex];
10489
+ const hasIcon = !!icon;
10490
+ const elementChildren = elementConfig?.children?.map(child => ({
10491
+ type: "attributeValue",
10492
+ ...child,
10493
+ attributeName: attribute,
10494
+ options: { noUnits: hasUnits, ...child.options },
10495
+ }));
10496
+ const attr = attribute
10497
+ ? layerInfo?.configuration?.attributesConfiguration?.attributes?.find(({ attributeName }) => attributeName === attribute)
10498
+ : null;
10499
+ if (hasIcon) {
10500
+ elementChildren[iconIndex] = {
10501
+ ...elementChildren[iconIndex],
10502
+ type: attr?.icon?.type?.toLowerCase(),
10503
+ value: attr?.icon?.resourceId || attr?.icon?.url || attr?.icon?.iconName,
10504
+ attributeName: null,
10505
+ };
10506
+ }
10507
+ const render = attribute
10508
+ ? getRenderElement({
10509
+ config,
10510
+ elementConfig: {
10511
+ ...elementConfig,
10512
+ children: elementChildren,
10513
+ },
10514
+ selectedTabId,
10515
+ attributes,
10516
+ layerInfo,
10517
+ type,
10518
+ })
10519
+ : renderElement;
10520
+ const value = render({ id: "value" });
10521
+ return {
10522
+ id,
10523
+ value,
10524
+ hideEmpty,
10525
+ style: innerTemplateStyle || style,
10526
+ hasIcon,
10527
+ hasUnits,
10528
+ render,
10529
+ };
10530
+ }, [config, layerInfo, selectedTabId, attributes, type, renderElement]);
10531
+ };
10532
+
10533
+ const useContainerAttributes = ({ elementConfig, type, renderElement }) => {
10534
+ const getRenderContainerItem = useRenderContainerItem(type, renderElement);
10535
+ const { options } = elementConfig || {};
10536
+ const { attributes: optionAttributes, useProjectHiddenAttributes } = options || {};
10537
+ const { attributes: contextAttributes, layerInfo } = useWidgetContext(type);
10538
+ const [hiddenAttributes] = useLayerHiddenAttributes(layerInfo?.name ?? "");
10539
+ const attributesToRender = React.useMemo(() => {
10540
+ if (!optionAttributes)
10541
+ return null;
10542
+ const baseList = optionAttributes.length === 0
10543
+ ? contextAttributes?.map(({ attributeName }) => attributeName) ?? []
10544
+ : optionAttributes;
10545
+ if (!useProjectHiddenAttributes)
10546
+ return baseList;
10547
+ return baseList.filter(attribute => !hiddenAttributes.includes(attribute));
10548
+ }, [optionAttributes, contextAttributes, hiddenAttributes, useProjectHiddenAttributes]);
10549
+ return { getRenderContainerItem, attributesToRender };
10550
+ };
10551
+
10552
+ const useFetchWithAuth = (url, transform, cleanup) => {
10553
+ const [data, setData] = React.useState(null);
10554
+ const loadingRef = React.useRef(false);
10555
+ const transformRef = React.useRef(transform);
10556
+ const cleanupRef = React.useRef(cleanup);
10557
+ transformRef.current = transform;
10558
+ cleanupRef.current = cleanup;
10559
+ const fetchData = React.useCallback(() => {
10560
+ if (!url || loadingRef.current)
10561
+ return;
10562
+ loadingRef.current = true;
10563
+ const token = window.localStorage.getItem(api.STORAGE_TOKEN_KEY);
10564
+ fetch(url, {
10565
+ headers: token ? { Authorization: `Bearer ${token}` } : {},
10566
+ })
10567
+ .then(res => (res.ok ? transformRef.current(res) : null))
10568
+ .then(setData)
10569
+ .catch(() => {
10570
+ setData(null);
10571
+ })
10572
+ .finally(() => {
10573
+ loadingRef.current = false;
10574
+ });
10575
+ }, [url]);
10576
+ React.useEffect(() => {
10577
+ if (url) {
10578
+ fetchData();
10579
+ }
10580
+ else {
10581
+ setData(null);
10582
+ }
10583
+ }, [url]);
10584
+ React.useEffect(() => () => {
10585
+ if (data !== null)
10586
+ cleanupRef.current?.(data);
10587
+ }, [data]);
10588
+ return data;
10589
+ };
10590
+
10591
+ const toObjectUrl = (res) => res.blob().then(blob => URL.createObjectURL(blob));
10592
+ const useFetchImageWithAuth = (url) => useFetchWithAuth(url, toObjectUrl, URL.revokeObjectURL);
10593
+
10594
+ const useGlobalContext = () => {
10595
+ const { t, language, themeName, api, ewktGeometry } = React.useContext(GlobalContext) || {};
10596
+ const translate = React.useCallback((value, options) => {
10597
+ if (t)
10598
+ return t(value, options);
10599
+ return options?.defaultValue ?? value;
10600
+ }, [t]);
10601
+ return React.useMemo(() => ({
10602
+ t: translate,
10603
+ language,
10604
+ themeName,
10605
+ api,
10606
+ ewktGeometry,
10607
+ }), [language, translate, api, ewktGeometry, themeName]);
10608
+ };
10609
+
10610
+ const HEIGHT_OFFSET = 20;
10611
+ const FILL_OPACITY = 0.28;
10612
+ const lineGenerator = line()
10613
+ .x(d => d[0])
10614
+ .y(d => d[1])
10615
+ .curve(monotoneX);
10616
+ const areaGenerator = (height) => area()
10617
+ .defined(d => d !== null)
10618
+ .x(d => d[0])
10619
+ .y0(height - HEIGHT_OFFSET)
10620
+ .y1(d => d[1])
10621
+ .curve(monotoneX);
10622
+ function getLinePoints(numPoints, svgLine) {
10623
+ if (!svgLine?.getTotalLength) {
10624
+ return [];
10625
+ }
10626
+ const lineLength = svgLine.getTotalLength();
10627
+ let interval;
10628
+ if (numPoints === 1) {
10629
+ interval = 0;
10630
+ }
10631
+ else {
10632
+ interval = lineLength / (numPoints - 1);
10633
+ }
10634
+ return range(numPoints)
10635
+ .filter(d => d * interval)
10636
+ .map(d => {
10637
+ const value = d * interval;
10638
+ const { x, y } = svgLine.getPointAtLength(value);
10639
+ return [x, y];
10640
+ });
10641
+ }
10642
+ function wrap() {
10643
+ const width = 80;
10644
+ const padding = 10;
10645
+ const self = select(this);
10646
+ let textLength = self.node().getComputedTextLength();
10647
+ let text = self.text();
10648
+ while (textLength > width - 2 * padding && text.length > 0) {
10649
+ text = text.slice(0, -1);
10650
+ self.text(`${text}...`);
10651
+ textLength = self.node().getComputedTextLength();
10652
+ }
10653
+ }
10654
+ const useChartChange = ({ dataSources, chartId, width, height, relatedAttributes, defaultColor, fontColor, markers, showMarkers, }) => {
10655
+ const { t } = useGlobalContext();
10656
+ const { layerInfos } = useWidgetContext();
10657
+ const strokeColors = relatedAttributes
10658
+ .filter(({ chartAxis }) => chartAxis === "y")
10659
+ .map(({ axisColor }) => axisColor);
10660
+ const ref = React.useRef({
10661
+ path: null,
10662
+ area: null,
10663
+ points: [],
10664
+ });
10665
+ const onChange = React.useCallback((range) => {
10666
+ const { path, area, points } = ref.current;
10667
+ let filteredPoints = [...points];
10668
+ if (range) {
10669
+ const hundred = 100;
10670
+ const x1 = +width * (range[0] / hundred);
10671
+ const x2 = +width * (range[1] / hundred);
10672
+ filteredPoints = points.filter(([x]) => x >= x1 && x <= x2);
10673
+ }
10674
+ path && path.attr("d", lineGenerator(filteredPoints));
10675
+ area && area.attr("d", areaGenerator(height)(filteredPoints));
10676
+ }, [height, width]);
10677
+ const customize = React.useCallback(({ svg }) => {
10678
+ svg.style("overflow", "visible");
10679
+ svg
10680
+ .selectAll(`.${charts.lineChartClassNames.lineChartXScaleGlobal} line,
10681
+ .${charts.lineChartClassNames.lineChartYScaleGlobal} line,
10682
+ .domain`)
10683
+ .each((_, index, nodes) => {
10684
+ nodes[index].remove();
10685
+ });
10686
+ svg
10687
+ .selectAll(`.${charts.lineChartClassNames.lineChartXScaleGlobal} .tick`)
10688
+ .each((_, index, nodes) => {
10689
+ if (!index) {
10690
+ nodes[index].style.textAnchor = "start";
10691
+ }
10692
+ if (index === nodes.length - 1) {
10693
+ nodes[index].style.textAnchor = "end";
10694
+ }
10695
+ if (markers) {
10696
+ nodes[index].remove();
10697
+ }
10698
+ if (showMarkers) {
10699
+ if (index % showMarkers !== 0) {
10700
+ nodes[index].remove();
10701
+ }
10702
+ }
10703
+ else {
10704
+ if (index && index < nodes.length - 1) {
10705
+ nodes[index].remove();
10706
+ }
10707
+ }
10708
+ });
10709
+ svg
10710
+ .selectAll(`.${charts.lineChartClassNames.lineChartXScaleGlobal} .tick text`)
10711
+ .attr("y", 16);
10712
+ svg
10713
+ .selectAll(`.${charts.lineChartClassNames.lineChartYScaleGlobal} .tick text`)
10714
+ .text(item => {
10715
+ if (!item)
10716
+ return 0;
10717
+ const dataSource = dataSources?.find(({ name }) => name === relatedAttributes[0].dataSourceName);
10718
+ const layerInfo = dataSource
10719
+ ? layerInfos.find(({ name }) => name === dataSource.layerName)
10720
+ : null;
10721
+ const attribute = layerInfo?.configuration?.attributesConfiguration?.attributes[relatedAttributes[0].attributeName];
10722
+ return attribute
10723
+ ? formatAttributeValue({
10724
+ t,
10725
+ type: attribute.type,
10726
+ value: item,
10727
+ stringFormat: attribute.stringFormat,
10728
+ noUnits: true,
10729
+ })
10730
+ : item;
10731
+ })
10732
+ .each(wrap);
10733
+ svg.selectAll(".tick text").attr("style", `color: ${fontColor}`);
10734
+ const global = svg.select(`.${charts.lineChartClassNames.lineChartLinesGlobal}`);
10735
+ const lineChartLines = svg.selectAll(`.${charts.lineChartClassNames.lineChartLine}`);
10736
+ const defs = [];
10737
+ lineChartLines.each((_, index, nodes) => {
10738
+ const lineChartLine = nodes[index];
10739
+ const gradientId = lodash.uniqueId(`${chartId}-gradient-`);
10740
+ const color = strokeColors[index] || defaultColor;
10741
+ const newPath = global
10742
+ .append("path")
10743
+ .attr("stroke", color)
10744
+ .attr("stroke-width", "0")
10745
+ .attr("class", charts.lineChartClassNames.lineChartLine);
10746
+ const points = getLinePoints(+width, lineChartLine);
10747
+ const area = global
10748
+ .append("path")
10749
+ .attr("fill", `url(#${gradientId})`)
10750
+ .attr("stroke-width", "0")
10751
+ .attr("fill-opacity", FILL_OPACITY);
10752
+ defs.push(`
10753
+ <linearGradient id="${gradientId}" x1="0" y1="0" x2="0" y2="1">
10754
+ <stop offset="0" stop-color="${color}" stop-opacity="1" />
10755
+ <stop offset="1" stop-color="${color}" stop-opacity="0" />
10756
+ </linearGradient>
10757
+ `);
10758
+ ref.current = {
10759
+ path: newPath,
10760
+ area,
10761
+ points,
10762
+ };
10763
+ onChange();
10764
+ });
10765
+ svg.append("defs").html(() => defs.join(""));
10766
+ }, [
10767
+ fontColor,
10768
+ dataSources,
10769
+ layerInfos,
10770
+ relatedAttributes,
10771
+ chartId,
10772
+ strokeColors,
10773
+ defaultColor,
10774
+ width,
10775
+ onChange,
10776
+ showMarkers,
10777
+ markers,
10778
+ ]);
10779
+ return [customize, onChange];
10780
+ };
10781
+
10776
10782
  const useWidgetConfig = (type = exports.WidgetType.Dashboard) => {
10777
10783
  const { config: configProp, containerIds, projectInfo, layerInfo, isEditing } = useWidgetContext(type);
10778
10784
  const config = React.useMemo(() => {
@@ -12741,6 +12747,7 @@ exports.useAppHeight = useAppHeight;
12741
12747
  exports.useAutoCompleteControl = useAutoCompleteControl;
12742
12748
  exports.useChartChange = useChartChange;
12743
12749
  exports.useChartData = useChartData;
12750
+ exports.useContainerAttributes = useContainerAttributes;
12744
12751
  exports.useCurrentPageLayers = useCurrentPageLayers;
12745
12752
  exports.useCustomFeatureSelect = useCustomFeatureSelect;
12746
12753
  exports.useDashboardHeader = useDashboardHeader;