@evergis/react 4.0.136 → 4.0.138
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 +10 -1
- package/dist/components/Dashboard/containers/AttachmentContainer/constants.d.ts +1 -0
- package/dist/components/Dashboard/containers/AttachmentContainer/utils/saveBlobAsFile.d.ts +7 -0
- package/dist/components/Dashboard/containers/VoteContainer/components/VoteCreateForm.d.ts +3 -0
- package/dist/components/Dashboard/containers/VoteContainer/components/VoteResults.d.ts +3 -0
- package/dist/components/Dashboard/containers/VoteContainer/components/VoteScreen.d.ts +9 -0
- package/dist/components/Dashboard/containers/VoteContainer/components/VoteShare.d.ts +3 -0
- package/dist/components/Dashboard/containers/VoteContainer/constants.d.ts +46 -0
- package/dist/components/Dashboard/containers/VoteContainer/hooks/useVoteActions.d.ts +26 -0
- package/dist/components/Dashboard/containers/VoteContainer/hooks/useVoteContainer.d.ts +25 -0
- package/dist/components/Dashboard/containers/VoteContainer/hooks/useVoteData.d.ts +22 -0
- package/dist/components/Dashboard/containers/VoteContainer/hooks/useVoteForm.d.ts +17 -0
- package/dist/components/Dashboard/containers/VoteContainer/hooks/useVoteUser.d.ts +14 -0
- package/dist/components/Dashboard/containers/VoteContainer/index.d.ts +11 -0
- package/dist/components/Dashboard/containers/VoteContainer/styled.d.ts +31 -0
- package/dist/components/Dashboard/containers/VoteContainer/types.d.ts +88 -0
- package/dist/components/Dashboard/containers/VoteContainer/utils.d.ts +38 -0
- package/dist/components/Dashboard/containers/index.d.ts +1 -0
- package/dist/components/Dashboard/containers/registry.d.ts +1 -0
- package/dist/components/Dashboard/hooks/index.d.ts +1 -0
- package/dist/components/Dashboard/hooks/useAttachmentDownload.d.ts +9 -0
- package/dist/components/Dashboard/types.d.ts +16 -2
- package/dist/index.js +1205 -406
- package/dist/index.js.map +1 -1
- package/dist/react.esm.js +1206 -409
- package/dist/react.esm.js.map +1 -1
- package/package.json +3 -3
package/dist/react.esm.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import styled, { createGlobalStyle, css, useTheme } from 'styled-components';
|
|
2
|
-
import { Icon, IconButtonInnerChild, IconButton, Flex, transition, Chip, shadows, Description, Divider, Popup, Menu, IconToggleButton, LinearProgress, Tooltip as Tooltip$1, IconToggle, Preview, LegendToggler, DropdownField, MultiSelectContainer, IconButtonButton, useDragAndDropEffect, FlatButton, DraggableTreeContainer, DraggableTree, FlexSpan, Dialog, DialogTitle, ActionsGroup, DialogContent, DialogActions, RaisedButton, UploaderItemArea, UploaderTitleWrapper,
|
|
2
|
+
import { Icon, IconButtonInnerChild, IconButton, Flex, transition, Chip, shadows, Description, Divider, Popup, Menu, IconToggleButton, LinearProgress, Tooltip as Tooltip$1, IconToggle, Preview, LegendToggler, DropdownField, MultiSelectContainer, IconButtonButton, useDragAndDropEffect, FlatButton, DraggableTreeContainer, DraggableTree, FlexSpan, Dialog, DialogTitle, ActionsGroup, DialogContent, DialogActions, RaisedButton, UploaderItemArea, UploaderTitleWrapper, RadioGroup, Dropdown, Input, Switch, Checkbox, Radio, ThemeProvider, darkTheme, CircularProgress, AutoComplete, Slider, DatePicker, getLocale, H2, Blank, Popover, NumberInput, Uploader, NumberRangeSlider, useAsyncAutocomplete, RangeNumberInput, TreeDropdown, defaultTheme, dateFormat } from '@evergis/uilib-gl';
|
|
3
3
|
import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
4
4
|
import { isValidElement, Fragment, createContext, memo, useRef, useState, useCallback, useEffect, useContext, useMemo, createElement, useLayoutEffect, forwardRef } from 'react';
|
|
5
5
|
import { barChartClassNames, lineChartClassNames, BarChart as BarChart$1, LineChart, PieChart } from '@evergis/charts';
|
|
6
6
|
import { AttributeType, AttributeIconType, generateId, STORAGE_TOKEN_KEY, parseJwt, STORAGE_REFRESH_TOKEN_KEY, RemoteTaskStatus, AttributeConfigurationType, LayerServiceType, OgcGeometryType, StringSubType } from '@evergis/api';
|
|
7
7
|
import { isNil, isEqual, isEmpty, uniqueId, unescape } from 'lodash';
|
|
8
|
-
import { Color as Color$1
|
|
8
|
+
import { ColorScale, Color as Color$1 } from '@evergis/color';
|
|
9
9
|
import { isValid, format, parseJSON, parseISO, toDate } from 'date-fns';
|
|
10
10
|
import { ru, enUS } from 'date-fns/locale';
|
|
11
11
|
import { stringify } from 'wkt';
|
|
12
12
|
import { toMercator, bbox } from '@turf/turf';
|
|
13
|
+
import { v4 } from 'uuid';
|
|
13
14
|
import { HubConnectionBuilder, HttpTransportType, LogLevel } from '@microsoft/signalr';
|
|
14
15
|
import MapboxDraw from '@mapbox/mapbox-gl-draw';
|
|
15
16
|
import { Swiper, SwiperSlide } from 'swiper/react';
|
|
@@ -19,7 +20,6 @@ import rehypeRaw from 'rehype-raw';
|
|
|
19
20
|
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
|
|
20
21
|
import remarkGfm from 'remark-gfm';
|
|
21
22
|
import MapGL, { Source, Layer as Layer$1 } from 'react-map-gl/maplibre';
|
|
22
|
-
import { v4 } from 'uuid';
|
|
23
23
|
import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css';
|
|
24
24
|
import 'mapbox-gl/dist/mapbox-gl.css';
|
|
25
25
|
import { jsPDF } from 'jspdf';
|
|
@@ -3295,6 +3295,7 @@ var ContainerTemplate;
|
|
|
3295
3295
|
ContainerTemplate["Attachment"] = "Attachment";
|
|
3296
3296
|
ContainerTemplate["Divider"] = "Divider";
|
|
3297
3297
|
ContainerTemplate["StructuredData"] = "StructuredData";
|
|
3298
|
+
ContainerTemplate["Vote"] = "Vote";
|
|
3298
3299
|
})(ContainerTemplate || (ContainerTemplate = {}));
|
|
3299
3300
|
var HeaderTemplate;
|
|
3300
3301
|
(function (HeaderTemplate) {
|
|
@@ -5377,169 +5378,6 @@ const getChartMarkers = (items, markers, dataSources) => {
|
|
|
5377
5378
|
})) || []);
|
|
5378
5379
|
};
|
|
5379
5380
|
|
|
5380
|
-
const ChartLegendContainer = styled(Flex).withConfig({ displayName: "ChartLegendContainer", componentId: "sc-1di1zl9" }) `
|
|
5381
|
-
flex-direction: column;
|
|
5382
|
-
flex-wrap: wrap;
|
|
5383
|
-
justify-content: ${({ twoColumns }) => (twoColumns ? "flex-start" : "center")};
|
|
5384
|
-
`;
|
|
5385
|
-
const ChartLegendItem = styled(Flex).withConfig({ displayName: "ChartLegendItem", componentId: "sc-16m9piw" }) `
|
|
5386
|
-
cursor: ${({ onClick }) => (onClick ? "pointer" : "default")};
|
|
5387
|
-
align-items: center;
|
|
5388
|
-
flex-wrap: nowrap;
|
|
5389
|
-
width: auto;
|
|
5390
|
-
margin-right: 0.375rem;
|
|
5391
|
-
margin-bottom: 0.25rem;
|
|
5392
|
-
line-height: 0.75rem;
|
|
5393
|
-
opacity: ${({ isFiltered, hasAnyFilter }) => (isFiltered ? 1 : hasAnyFilter ? FILTERED_VALUE_OPACITY / 100 : 1)};
|
|
5394
|
-
`;
|
|
5395
|
-
const ChartLegendColor = styled.div.withConfig({ displayName: "ChartLegendColor", componentId: "sc-zxnnio" }) `
|
|
5396
|
-
width: 0.5rem;
|
|
5397
|
-
height: 0.5rem;
|
|
5398
|
-
margin-right: 0.375rem;
|
|
5399
|
-
background-color: ${({ color }) => color};
|
|
5400
|
-
border-radius: ${({ theme: { borderRadius } }) => borderRadius.tiny};
|
|
5401
|
-
`;
|
|
5402
|
-
const ChartLegendName = styled.div.withConfig({ displayName: "ChartLegendName", componentId: "sc-isz576" }) `
|
|
5403
|
-
flex: 1;
|
|
5404
|
-
font-size: ${({ $fontSize }) => $fontSize || "0.625rem"};
|
|
5405
|
-
color: ${({ theme: { palette }, $fontColor }) => $fontColor || palette.textPrimary};
|
|
5406
|
-
`;
|
|
5407
|
-
|
|
5408
|
-
const ContainerAlias = styled(Flex).withConfig({ displayName: "ContainerAlias", componentId: "sc-1ftaxyu" }) `
|
|
5409
|
-
align-items: center;
|
|
5410
|
-
flex-wrap: nowrap;
|
|
5411
|
-
font-size: 0.75rem;
|
|
5412
|
-
color: ${({ theme: { palette } }) => palette.textSecondary};
|
|
5413
|
-
|
|
5414
|
-
&&& {
|
|
5415
|
-
margin-bottom: ${({ hasBottomMargin }) => (hasBottomMargin ? 0.25 : 0)}rem;
|
|
5416
|
-
}
|
|
5417
|
-
|
|
5418
|
-
span[kind] {
|
|
5419
|
-
margin-right: 0.5rem;
|
|
5420
|
-
|
|
5421
|
-
:after {
|
|
5422
|
-
color: ${({ theme: { palette } }) => palette.primary};
|
|
5423
|
-
}
|
|
5424
|
-
}
|
|
5425
|
-
`;
|
|
5426
|
-
const ContainerAliasIcon = styled.div.withConfig({ displayName: "ContainerAliasIcon", componentId: "sc-elezmi" }) `
|
|
5427
|
-
margin-right: 0.5rem;
|
|
5428
|
-
`;
|
|
5429
|
-
const ContainerChart = styled(Flex).withConfig({ displayName: "ContainerChart", componentId: "sc-16sh3wn" }) `
|
|
5430
|
-
justify-content: flex-start;
|
|
5431
|
-
|
|
5432
|
-
> * {
|
|
5433
|
-
display: flex;
|
|
5434
|
-
justify-content: center;
|
|
5435
|
-
width: 100%;
|
|
5436
|
-
}
|
|
5437
|
-
|
|
5438
|
-
/* fill: слот забирает остаток высоты ячейки (под слотами title/alias/legend) и центрирует тело графика. */
|
|
5439
|
-
${({ $fill }) => $fill &&
|
|
5440
|
-
css `
|
|
5441
|
-
flex: 1 1 auto;
|
|
5442
|
-
min-height: 0;
|
|
5443
|
-
align-items: center;
|
|
5444
|
-
justify-content: center;
|
|
5445
|
-
|
|
5446
|
-
> * {
|
|
5447
|
-
height: 100%;
|
|
5448
|
-
}
|
|
5449
|
-
`}
|
|
5450
|
-
`;
|
|
5451
|
-
const ContainerLegend = styled(Flex).withConfig({ displayName: "ContainerLegend", componentId: "sc-z1n1s8" }) ``;
|
|
5452
|
-
const ContainerUnits = styled.div.withConfig({ displayName: "ContainerUnits", componentId: "sc-1vurgy9" }) `
|
|
5453
|
-
margin-left: 0.5rem;
|
|
5454
|
-
white-space: nowrap;
|
|
5455
|
-
font-size: 0.75rem;
|
|
5456
|
-
`;
|
|
5457
|
-
const ContainerValue = styled(Flex).withConfig({ displayName: "ContainerValue", componentId: "sc-aj1rs9" }) `
|
|
5458
|
-
justify-content: flex-end;
|
|
5459
|
-
align-items: center;
|
|
5460
|
-
flex-wrap: nowrap;
|
|
5461
|
-
width: 100%;
|
|
5462
|
-
font-size: ${({ big }) => (big ? 1.5 : 1)}rem;
|
|
5463
|
-
color: ${({ fontColor, theme: { palette } }) => fontColor || palette.textPrimary};
|
|
5464
|
-
|
|
5465
|
-
/* fill: строка со слотом chart+legend забирает остаток высоты Container'а (гейт — только у Chart-контейнера). */
|
|
5466
|
-
${({ $fill }) => $fill &&
|
|
5467
|
-
css `
|
|
5468
|
-
flex: 1 1 auto;
|
|
5469
|
-
min-height: 0;
|
|
5470
|
-
`}
|
|
5471
|
-
|
|
5472
|
-
> * {
|
|
5473
|
-
width: ${({ column }) => (column ? "100%" : "auto")};
|
|
5474
|
-
}
|
|
5475
|
-
|
|
5476
|
-
${ContainerChart}, ${ContainerLegend} {
|
|
5477
|
-
width: ${({ column }) => (column ? "100%" : "50%")};
|
|
5478
|
-
}
|
|
5479
|
-
|
|
5480
|
-
${ContainerLegend} {
|
|
5481
|
-
margin-left: ${({ column }) => (column ? 0 : "1rem")};
|
|
5482
|
-
}
|
|
5483
|
-
|
|
5484
|
-
${ChartLegendContainer} {
|
|
5485
|
-
flex-direction: ${({ column }) => (column ? "row" : "column")};
|
|
5486
|
-
margin-top: ${({ column }) => (column ? "1rem" : 0)};
|
|
5487
|
-
}
|
|
5488
|
-
`;
|
|
5489
|
-
const ColorIconMixin = css `
|
|
5490
|
-
:after {
|
|
5491
|
-
color: ${({ $fontColor }) => $fontColor} !important;
|
|
5492
|
-
}
|
|
5493
|
-
`;
|
|
5494
|
-
const SizeIconMixin = css `
|
|
5495
|
-
:after {
|
|
5496
|
-
font-size: ${({ $fontSize }) => $fontSize}px !important;
|
|
5497
|
-
}
|
|
5498
|
-
`;
|
|
5499
|
-
styled(Icon).withConfig({ displayName: "ContainerIcon", componentId: "sc-1w9w0bq" }) `
|
|
5500
|
-
width: auto;
|
|
5501
|
-
height: auto;
|
|
5502
|
-
margin-bottom: 0.5rem;
|
|
5503
|
-
${({ $fontColor }) => !!$fontColor && ColorIconMixin};
|
|
5504
|
-
${({ $fontSize }) => !!$fontSize && SizeIconMixin};
|
|
5505
|
-
`;
|
|
5506
|
-
const SvgContainerColorMixin$1 = css `
|
|
5507
|
-
path,
|
|
5508
|
-
line,
|
|
5509
|
-
circle {
|
|
5510
|
-
fill: ${({ $fontColor }) => $fontColor};
|
|
5511
|
-
}
|
|
5512
|
-
`;
|
|
5513
|
-
const SvgContainer$1 = styled.div.withConfig({ displayName: "SvgContainer", componentId: "sc-1qs5gmx" }) `
|
|
5514
|
-
&&& {
|
|
5515
|
-
min-width: ${({ $width }) => ($width ? `${$width}px` : "1rem")};
|
|
5516
|
-
max-width: ${({ $width }) => ($width ? `${$width}px` : "1rem")};
|
|
5517
|
-
|
|
5518
|
-
${({ $fontColor }) => !!$fontColor && SvgContainerColorMixin$1};
|
|
5519
|
-
|
|
5520
|
-
> * {
|
|
5521
|
-
min-width: inherit;
|
|
5522
|
-
}
|
|
5523
|
-
}
|
|
5524
|
-
`;
|
|
5525
|
-
const TwoColumnContainerWrapper = styled(Container).withConfig({ displayName: "TwoColumnContainerWrapper", componentId: "sc-1ogv4d3" }) `
|
|
5526
|
-
flex-direction: row;
|
|
5527
|
-
flex-wrap: nowrap;
|
|
5528
|
-
align-items: center;
|
|
5529
|
-
|
|
5530
|
-
> * {
|
|
5531
|
-
flex: 1;
|
|
5532
|
-
}
|
|
5533
|
-
|
|
5534
|
-
> ${ContainerValue} {
|
|
5535
|
-
justify-content: flex-end;
|
|
5536
|
-
|
|
5537
|
-
> * {
|
|
5538
|
-
text-align: right;
|
|
5539
|
-
}
|
|
5540
|
-
}
|
|
5541
|
-
`;
|
|
5542
|
-
|
|
5543
5381
|
const DashboardContext = createContext({});
|
|
5544
5382
|
const DashboardProvider = memo(({ children, ...props }) => {
|
|
5545
5383
|
return jsx(DashboardContext.Provider, { value: props, children: children });
|
|
@@ -5692,6 +5530,127 @@ const ServerNotificationsProvider = ({ url, initialized, apiClient, children })
|
|
|
5692
5530
|
}, children: children }));
|
|
5693
5531
|
};
|
|
5694
5532
|
|
|
5533
|
+
const useGlobalContext = () => {
|
|
5534
|
+
const { t, language, themeName, api, ewktGeometry, ewktExtent, zoomLevel, notification } = useContext(GlobalContext) || {};
|
|
5535
|
+
const translate = useCallback((value, options) => {
|
|
5536
|
+
if (t)
|
|
5537
|
+
return t(value, options);
|
|
5538
|
+
return options?.defaultValue ?? value;
|
|
5539
|
+
}, [t]);
|
|
5540
|
+
return useMemo(() => ({
|
|
5541
|
+
t: translate,
|
|
5542
|
+
language,
|
|
5543
|
+
themeName,
|
|
5544
|
+
api,
|
|
5545
|
+
ewktGeometry,
|
|
5546
|
+
ewktExtent,
|
|
5547
|
+
zoomLevel,
|
|
5548
|
+
notification,
|
|
5549
|
+
}), [language, translate, api, ewktGeometry, ewktExtent, zoomLevel, themeName, notification]);
|
|
5550
|
+
};
|
|
5551
|
+
|
|
5552
|
+
const GRID_TILE_SIZE = "4.5rem";
|
|
5553
|
+
const LIST_ICON_SIZE = "1.5rem";
|
|
5554
|
+
const JPG_MIME_TYPE = "image/jpeg";
|
|
5555
|
+
const PNG_MIME_TYPE = "image/png";
|
|
5556
|
+
const IMAGE_MIME_TYPES = [
|
|
5557
|
+
"image/apng",
|
|
5558
|
+
"image/avif",
|
|
5559
|
+
"image/gif",
|
|
5560
|
+
"image/jpeg",
|
|
5561
|
+
"image/png",
|
|
5562
|
+
"image/svg+xml",
|
|
5563
|
+
"image/webp",
|
|
5564
|
+
];
|
|
5565
|
+
const XLSX_MIME_TYPES = [
|
|
5566
|
+
"application/vnd.ms-excel",
|
|
5567
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
5568
|
+
];
|
|
5569
|
+
const PDF_MIME_TYPE = "application/pdf";
|
|
5570
|
+
const DOCX_MIME_TYPES = [
|
|
5571
|
+
"application/msword",
|
|
5572
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
5573
|
+
];
|
|
5574
|
+
const CSV_MIME_TYPE = "text/csv";
|
|
5575
|
+
const JSON_MIME_TYPE = "application/json";
|
|
5576
|
+
const TXT_MIME_TYPE = "text/plain";
|
|
5577
|
+
const PPTX_MIME_TYPES = [
|
|
5578
|
+
"application/vnd.ms-powerpoint",
|
|
5579
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
5580
|
+
];
|
|
5581
|
+
const SHP_MIME_TYPE = "application/octet-stream";
|
|
5582
|
+
const KML_MIME_TYPE = "application/octet-stream";
|
|
5583
|
+
const ZIP_MIME_TYPE = "application/zip";
|
|
5584
|
+
const PYTHON_MIME_TYPES = ["application/x-python-code", "text/x-python"];
|
|
5585
|
+
const DOWNLOAD_ERROR_DURATION = 5000;
|
|
5586
|
+
var AddAttachmentSource;
|
|
5587
|
+
(function (AddAttachmentSource) {
|
|
5588
|
+
AddAttachmentSource["Pc"] = "pc";
|
|
5589
|
+
AddAttachmentSource["Catalog"] = "catalog";
|
|
5590
|
+
AddAttachmentSource["Link"] = "link";
|
|
5591
|
+
})(AddAttachmentSource || (AddAttachmentSource = {}));
|
|
5592
|
+
|
|
5593
|
+
const REVOKE_DELAY = 1000;
|
|
5594
|
+
/**
|
|
5595
|
+
* Отдаёт браузеру уже загруженный файл под именем `fileName`.
|
|
5596
|
+
*
|
|
5597
|
+
* Адрес отзывается не сразу: часть браузеров дочитывает поток уже после клика, и мгновенный
|
|
5598
|
+
* `revokeObjectURL` обрывает сохранение.
|
|
5599
|
+
*/
|
|
5600
|
+
const saveBlobAsFile = (blob, fileName) => {
|
|
5601
|
+
const url = URL.createObjectURL(blob);
|
|
5602
|
+
const link = document.createElement("a");
|
|
5603
|
+
link.href = url;
|
|
5604
|
+
link.download = fileName;
|
|
5605
|
+
link.style.display = "none";
|
|
5606
|
+
document.body.appendChild(link);
|
|
5607
|
+
link.click();
|
|
5608
|
+
document.body.removeChild(link);
|
|
5609
|
+
window.setTimeout(() => URL.revokeObjectURL(url), REVOKE_DELAY);
|
|
5610
|
+
};
|
|
5611
|
+
|
|
5612
|
+
/**
|
|
5613
|
+
* Скачивание вложения по требованию — файл запрашивается в момент клика, а не заранее.
|
|
5614
|
+
*
|
|
5615
|
+
* Свой файл лежит за авторизацией (`Authorization: Bearer`), поэтому ссылкой его не отдать:
|
|
5616
|
+
* он тянется через api и сохраняется из памяти. Чужой открывается ссылкой — кросс-доменный
|
|
5617
|
+
* `download` браузер всё равно игнорирует.
|
|
5618
|
+
*/
|
|
5619
|
+
const useAttachmentDownload = (items) => {
|
|
5620
|
+
const { api, notification, t } = useGlobalContext();
|
|
5621
|
+
const inFlightRef = useRef(new Set());
|
|
5622
|
+
return useCallback((index) => {
|
|
5623
|
+
const item = items[index];
|
|
5624
|
+
if (!item)
|
|
5625
|
+
return;
|
|
5626
|
+
if (item.isExternal) {
|
|
5627
|
+
window.open(item.link, "_blank", "noopener,noreferrer");
|
|
5628
|
+
return;
|
|
5629
|
+
}
|
|
5630
|
+
if (!api?.catalog?.getFile || inFlightRef.current.has(item.link))
|
|
5631
|
+
return;
|
|
5632
|
+
inFlightRef.current.add(item.link);
|
|
5633
|
+
api.catalog
|
|
5634
|
+
.getFile(item.link)
|
|
5635
|
+
.then(blob => saveBlobAsFile(blob, item.name))
|
|
5636
|
+
.catch(error => {
|
|
5637
|
+
notification?.add({
|
|
5638
|
+
id: v4(),
|
|
5639
|
+
title: t("attachments.downloadError", {
|
|
5640
|
+
ns: "common",
|
|
5641
|
+
defaultValue: "Не удалось скачать вложение",
|
|
5642
|
+
}),
|
|
5643
|
+
description: error instanceof Error ? error.message : item.name,
|
|
5644
|
+
error: true,
|
|
5645
|
+
duration: DOWNLOAD_ERROR_DURATION,
|
|
5646
|
+
});
|
|
5647
|
+
})
|
|
5648
|
+
.finally(() => {
|
|
5649
|
+
inFlightRef.current.delete(item.link);
|
|
5650
|
+
});
|
|
5651
|
+
}, [items, api, notification, t]);
|
|
5652
|
+
};
|
|
5653
|
+
|
|
5695
5654
|
/**
|
|
5696
5655
|
* Контекст виджет-фрейма. Возвращаемый объект включает поля и {@link DashboardContext},
|
|
5697
5656
|
* и {@link FeatureCardContext}, а гибридные (`config`, `isEditing`, `isLoading`, `pageIndex`,
|
|
@@ -5808,65 +5767,6 @@ const useAttachmentItems = ({ type, elementConfig, valueOverride, }) => {
|
|
|
5808
5767
|
};
|
|
5809
5768
|
};
|
|
5810
5769
|
|
|
5811
|
-
const useGlobalContext = () => {
|
|
5812
|
-
const { t, language, themeName, api, ewktGeometry, ewktExtent, zoomLevel, notification } = useContext(GlobalContext) || {};
|
|
5813
|
-
const translate = useCallback((value, options) => {
|
|
5814
|
-
if (t)
|
|
5815
|
-
return t(value, options);
|
|
5816
|
-
return options?.defaultValue ?? value;
|
|
5817
|
-
}, [t]);
|
|
5818
|
-
return useMemo(() => ({
|
|
5819
|
-
t: translate,
|
|
5820
|
-
language,
|
|
5821
|
-
themeName,
|
|
5822
|
-
api,
|
|
5823
|
-
ewktGeometry,
|
|
5824
|
-
ewktExtent,
|
|
5825
|
-
zoomLevel,
|
|
5826
|
-
notification,
|
|
5827
|
-
}), [language, translate, api, ewktGeometry, ewktExtent, zoomLevel, themeName, notification]);
|
|
5828
|
-
};
|
|
5829
|
-
|
|
5830
|
-
const GRID_TILE_SIZE = "4.5rem";
|
|
5831
|
-
const LIST_ICON_SIZE = "1.5rem";
|
|
5832
|
-
const JPG_MIME_TYPE = "image/jpeg";
|
|
5833
|
-
const PNG_MIME_TYPE = "image/png";
|
|
5834
|
-
const IMAGE_MIME_TYPES = [
|
|
5835
|
-
"image/apng",
|
|
5836
|
-
"image/avif",
|
|
5837
|
-
"image/gif",
|
|
5838
|
-
"image/jpeg",
|
|
5839
|
-
"image/png",
|
|
5840
|
-
"image/svg+xml",
|
|
5841
|
-
"image/webp",
|
|
5842
|
-
];
|
|
5843
|
-
const XLSX_MIME_TYPES = [
|
|
5844
|
-
"application/vnd.ms-excel",
|
|
5845
|
-
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
5846
|
-
];
|
|
5847
|
-
const PDF_MIME_TYPE = "application/pdf";
|
|
5848
|
-
const DOCX_MIME_TYPES = [
|
|
5849
|
-
"application/msword",
|
|
5850
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
5851
|
-
];
|
|
5852
|
-
const CSV_MIME_TYPE = "text/csv";
|
|
5853
|
-
const JSON_MIME_TYPE = "application/json";
|
|
5854
|
-
const TXT_MIME_TYPE = "text/plain";
|
|
5855
|
-
const PPTX_MIME_TYPES = [
|
|
5856
|
-
"application/vnd.ms-powerpoint",
|
|
5857
|
-
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
5858
|
-
];
|
|
5859
|
-
const SHP_MIME_TYPE = "application/octet-stream";
|
|
5860
|
-
const KML_MIME_TYPE = "application/octet-stream";
|
|
5861
|
-
const ZIP_MIME_TYPE = "application/zip";
|
|
5862
|
-
const PYTHON_MIME_TYPES = ["application/x-python-code", "text/x-python"];
|
|
5863
|
-
var AddAttachmentSource;
|
|
5864
|
-
(function (AddAttachmentSource) {
|
|
5865
|
-
AddAttachmentSource["Pc"] = "pc";
|
|
5866
|
-
AddAttachmentSource["Catalog"] = "catalog";
|
|
5867
|
-
AddAttachmentSource["Link"] = "link";
|
|
5868
|
-
})(AddAttachmentSource || (AddAttachmentSource = {}));
|
|
5869
|
-
|
|
5870
5770
|
var FileType;
|
|
5871
5771
|
(function (FileType) {
|
|
5872
5772
|
FileType[FileType["UNKNOWN"] = 0] = "UNKNOWN";
|
|
@@ -8603,61 +8503,6 @@ const useUpdateDataSource = ({ dataSource, config, filters, attributes, layerPar
|
|
|
8603
8503
|
}, [dataSource, getDataSourcePromises, getUpdatedDataSources, dataSources]);
|
|
8604
8504
|
};
|
|
8605
8505
|
|
|
8606
|
-
const useRenderContainer = ({ elementConfig, type, renderElement, renderBody, }) => {
|
|
8607
|
-
const { attributes } = useWidgetContext(type);
|
|
8608
|
-
const { getRenderContainerItem, attributesToRender } = useContainerAttributes({
|
|
8609
|
-
elementConfig,
|
|
8610
|
-
type,
|
|
8611
|
-
renderElement,
|
|
8612
|
-
});
|
|
8613
|
-
const renderContainer = useCallback((attribute) => {
|
|
8614
|
-
const item = getRenderContainerItem(elementConfig, attribute);
|
|
8615
|
-
const itemAttribute = attribute
|
|
8616
|
-
? attributes?.find(({ attributeName }) => attributeName === attribute)
|
|
8617
|
-
: undefined;
|
|
8618
|
-
const overrideTemplateName = getDisplayTemplateNameFromAttribute(itemAttribute);
|
|
8619
|
-
if (overrideTemplateName) {
|
|
8620
|
-
const OverrideContainer = getContainerComponent(overrideTemplateName);
|
|
8621
|
-
if (OverrideContainer) {
|
|
8622
|
-
const itemConfig = {
|
|
8623
|
-
...elementConfig,
|
|
8624
|
-
children: (elementConfig?.children ?? []).map(child => ({
|
|
8625
|
-
...child,
|
|
8626
|
-
attributeName: attribute,
|
|
8627
|
-
})),
|
|
8628
|
-
templateName: overrideTemplateName,
|
|
8629
|
-
};
|
|
8630
|
-
return (jsx(OverrideContainer, { type: type, elementConfig: itemConfig, renderElement: item.render }, attribute));
|
|
8631
|
-
}
|
|
8632
|
-
}
|
|
8633
|
-
if (isEmptyElementValue(item.value) && item.hideEmpty)
|
|
8634
|
-
return null;
|
|
8635
|
-
return renderBody(item, attribute);
|
|
8636
|
-
}, [getRenderContainerItem, elementConfig, attributes, type, renderBody]);
|
|
8637
|
-
return { renderContainer, attributesToRender };
|
|
8638
|
-
};
|
|
8639
|
-
|
|
8640
|
-
const OneColumnContainer = memo(({ type, elementConfig, renderElement }) => {
|
|
8641
|
-
const { width, height, overflow } = elementConfig?.options || {};
|
|
8642
|
-
const templateName = elementConfig?.templateName;
|
|
8643
|
-
const bgHost = useBgImageHost(elementConfig);
|
|
8644
|
-
const renderBody = useCallback(({ id, value, style, hasUnits, render }) => (jsxs(Container, { id: id, isColumn: true, "data-templatename": templateName, style: style, "$sizeCss": getWrapperSizeStyle({
|
|
8645
|
-
style,
|
|
8646
|
-
width,
|
|
8647
|
-
height,
|
|
8648
|
-
overflow,
|
|
8649
|
-
defaults: BASE_CONTAINER_STYLE,
|
|
8650
|
-
defaultWidth: FILL_SIZE,
|
|
8651
|
-
}), ...bgHost, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: render }), jsxs(ContainerAlias, { hasBottomMargin: true, children: [render({ id: "alias" }), render({ id: "tooltip" }), render({ id: "modal" })] }), jsxs(ContainerValue, { children: [value, hasUnits && (jsx(ContainerUnits, { children: render({ id: "units" }) }))] })] })), [width, height, overflow, templateName, bgHost, elementConfig]);
|
|
8652
|
-
const { renderContainer, attributesToRender } = useRenderContainer({
|
|
8653
|
-
type,
|
|
8654
|
-
elementConfig,
|
|
8655
|
-
renderElement,
|
|
8656
|
-
renderBody,
|
|
8657
|
-
});
|
|
8658
|
-
return attributesToRender ? (jsx(Fragment$1, { children: attributesToRender.map(renderContainer) })) : (renderContainer());
|
|
8659
|
-
});
|
|
8660
|
-
|
|
8661
8506
|
/** Обычная (не сеточная) раскладка группы: дети в колонку или в строку. */
|
|
8662
8507
|
const FlowGroup = memo(({ elementConfig, type, renderElement }) => {
|
|
8663
8508
|
const { expandedContainers } = useWidgetContext(type);
|
|
@@ -9200,145 +9045,310 @@ const AttachmentContainer = memo(({ type, elementConfig, renderElement }) => {
|
|
|
9200
9045
|
setPreviewIndex(idx);
|
|
9201
9046
|
}, [items]);
|
|
9202
9047
|
const handleClosePreview = useCallback(() => setPreviewIndex(null), []);
|
|
9048
|
+
const downloadByIndex = useAttachmentDownload(items);
|
|
9049
|
+
const handleDownload = useCallback((_image, index) => downloadByIndex(index), [downloadByIndex]);
|
|
9203
9050
|
const handleShowMore = useCallback(() => setShowMore(true), [setShowMore]);
|
|
9204
9051
|
const isVisible = isVisibleContainer(id, expandable, expanded, expandedContainers);
|
|
9205
|
-
return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsx(Container, { ...body, children: jsxs(Flex, { column: true, children: [jsx(AttachmentsHeader, { alias: renderElement?.({ id: "alias" }), count: items.length, viewMode: viewMode, onChangeViewMode: setViewMode }), jsx(AttachmentsContent, { children: viewMode === "grid" ? (jsx(AttachmentsGrid, { items: visibleItems, isEdit: false, onPreview: handlePreview })) : (jsx(AttachmentsList, { items: visibleItems, isEdit: false, onPreview: handlePreview })) }), hasMore && !showMore && (jsx(ShowMoreButton, { hiddenCount: hiddenCount, onClick: handleShowMore })), previewIndex !== null && (jsx(Preview, { images: previewImages, initialIndex: previewIndex, isOpen: previewIndex !== null, onClose: handleClosePreview, errorTitleText: t("attachments.resourceUnavailable", {
|
|
9052
|
+
return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsx(Container, { ...body, children: jsxs(Flex, { column: true, children: [jsx(AttachmentsHeader, { alias: renderElement?.({ id: "alias" }), count: items.length, viewMode: viewMode, onChangeViewMode: setViewMode }), jsx(AttachmentsContent, { children: viewMode === "grid" ? (jsx(AttachmentsGrid, { items: visibleItems, isEdit: false, onPreview: handlePreview })) : (jsx(AttachmentsList, { items: visibleItems, isEdit: false, onPreview: handlePreview })) }), hasMore && !showMore && (jsx(ShowMoreButton, { hiddenCount: hiddenCount, onClick: handleShowMore })), previewIndex !== null && (jsx(Preview, { images: previewImages, initialIndex: previewIndex, isOpen: previewIndex !== null, onClose: handleClosePreview, onDownload: handleDownload, errorTitleText: t("attachments.resourceUnavailable", {
|
|
9206
9053
|
ns: "common",
|
|
9207
9054
|
defaultValue: "Ресурс недоступен",
|
|
9208
9055
|
}) }, previewIndex))] }) }))] }));
|
|
9209
9056
|
});
|
|
9210
9057
|
|
|
9211
|
-
const
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsxs(Container, { ...body, children: [jsx(ContainerAlias, { hasBottomMargin: true, children: renderElement({ id: "alias" }) }), jsx(ContainerValue, { children: renderElement({ id: "value", wrap: false }) })] }))] }));
|
|
9218
|
-
});
|
|
9219
|
-
|
|
9220
|
-
/**
|
|
9221
|
-
* Контекст вписывания графика (`options.fill` контейнера Chart).
|
|
9222
|
-
*
|
|
9223
|
-
* Компонент `Chart` рендерится через `renderElement({ id: "chart" })` и получает только дочерний
|
|
9224
|
-
* узел `{ id: "chart" }` — опции контейнера до него не доходят. `ChartContainer` оборачивает
|
|
9225
|
-
* график в провайдер этого контекста, а `Chart` читает значение через `useContext`.
|
|
9226
|
-
*/
|
|
9227
|
-
const FillContext = createContext({ fill: false, fitHeight: false });
|
|
9228
|
-
|
|
9229
|
-
const ChartContainer = memo(({ elementConfig, isVisible, type, renderElement }) => {
|
|
9230
|
-
const { options, children } = elementConfig || {};
|
|
9231
|
-
const { twoColumns, hideEmpty, height } = options || {};
|
|
9232
|
-
const fill = options?.fill;
|
|
9233
|
-
const fillContextValue = useMemo(() => ({ fill: !!fill, fitHeight: !!fill && height != null }), [fill, height]);
|
|
9234
|
-
const aliasElement = children.find(child => child.id === "alias");
|
|
9235
|
-
const chartElement = children.find(child => child.id === "chart");
|
|
9236
|
-
const legendElement = children.find(child => child.id === "legend");
|
|
9237
|
-
const { root, body } = useContainerRoot({ elementConfig });
|
|
9238
|
-
const { data, loading } = useChartData({
|
|
9239
|
-
element: chartElement,
|
|
9240
|
-
type,
|
|
9241
|
-
});
|
|
9242
|
-
if (data[0] && !data[0].items) {
|
|
9243
|
-
return jsx(DataSourceError, { name: elementConfig.templateName });
|
|
9244
|
-
}
|
|
9245
|
-
const hasItems = !!data[0]?.items?.length;
|
|
9246
|
-
if (!loading && !hasItems && hideEmpty)
|
|
9247
|
-
return null;
|
|
9248
|
-
return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsxs(Container, { ...body, isColumn: true, children: [aliasElement && jsx(ContainerAlias, { hasBottomMargin: true, children: renderElement({ id: "alias" }) }), jsx(ContainerValue, { column: !twoColumns, alignItems: twoColumns && fill ? "stretch" : "center", "$fill": !!fill, children: hasItems ? (jsxs(Fragment$1, { children: [jsx(ContainerChart, { "$fill": !!fill, children: jsx(FillContext.Provider, { value: fillContextValue, children: renderElement({ id: "chart" }) }) }), jsx(ContainerLegend, { justifyContent: legendElement?.options?.center ? "center" : "flex-start", children: renderElement({ id: "legend" }) })] })) : (jsx(Fragment$1, { children: "\u2014" })) })] }))] }));
|
|
9249
|
-
});
|
|
9250
|
-
|
|
9251
|
-
const InnerContainerWrapper = styled.div.withConfig({ displayName: "InnerContainerWrapper", componentId: "sc-1a1fl7q" }) `
|
|
9058
|
+
const ChartLegendContainer = styled(Flex).withConfig({ displayName: "ChartLegendContainer", componentId: "sc-1di1zl9" }) `
|
|
9059
|
+
flex-direction: column;
|
|
9060
|
+
flex-wrap: wrap;
|
|
9061
|
+
justify-content: ${({ twoColumns }) => (twoColumns ? "flex-start" : "center")};
|
|
9062
|
+
`;
|
|
9063
|
+
const ChartLegendItem = styled(Flex).withConfig({ displayName: "ChartLegendItem", componentId: "sc-16m9piw" }) `
|
|
9252
9064
|
cursor: ${({ onClick }) => (onClick ? "pointer" : "default")};
|
|
9253
|
-
|
|
9254
|
-
|
|
9255
|
-
|
|
9256
|
-
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9260
|
-
вместе с ней. Записи вылезали за свои обёртки и наезжали друг на друга.
|
|
9261
|
-
*/
|
|
9262
|
-
${({ column }) => !column && "flex-shrink: 0;"}
|
|
9065
|
+
align-items: center;
|
|
9066
|
+
flex-wrap: nowrap;
|
|
9067
|
+
width: auto;
|
|
9068
|
+
margin-right: 0.375rem;
|
|
9069
|
+
margin-bottom: 0.25rem;
|
|
9070
|
+
line-height: 0.75rem;
|
|
9071
|
+
opacity: ${({ isFiltered, hasAnyFilter }) => (isFiltered ? 1 : hasAnyFilter ? FILTERED_VALUE_OPACITY / 100 : 1)};
|
|
9263
9072
|
`;
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
type,
|
|
9271
|
-
elementConfig,
|
|
9272
|
-
dataSources,
|
|
9273
|
-
feature,
|
|
9274
|
-
});
|
|
9275
|
-
const { options, children } = elementConfig || {};
|
|
9276
|
-
const { relatedDataSource, filterName, column } = options || {};
|
|
9277
|
-
/**
|
|
9278
|
-
* Конфиг записи — тот же узел хоста, но без слота фона.
|
|
9279
|
-
*
|
|
9280
|
-
* У `DataSource`/`DataSourceProgress` дети — слоты ВНУТРЕННЕГО шаблона, поэтому `bgImage`
|
|
9281
|
-
* достался бы и хосту, и каждой записи: одна картинка нарисовалась бы дважды. Фон остаётся
|
|
9282
|
-
* за хостом (он и есть контейнер), а записи получают конфиг без слота.
|
|
9283
|
-
*/
|
|
9284
|
-
const innerConfig = useMemo(() => elementConfig && children?.some(({ id }) => id === BG_IMAGE_SLOT_ID)
|
|
9285
|
-
? { ...elementConfig, children: children.filter(({ id }) => id !== BG_IMAGE_SLOT_ID) }
|
|
9286
|
-
: elementConfig, [elementConfig, children]);
|
|
9287
|
-
const aliasElement = useMemo(() => children?.find(({ id }) => id === "alias"), [children]);
|
|
9288
|
-
const valueElement = useMemo(() => children?.find(({ id }) => id === "value"), [children]);
|
|
9289
|
-
const aliasAttribute = useMemo(() => (aliasElement ? attributes?.find(({ attributeName }) => attributeName === aliasElement.attributeName) : null), [aliasElement, attributes]);
|
|
9290
|
-
const value = aliasAttribute?.value;
|
|
9291
|
-
const InnerContainer = innerComponent;
|
|
9292
|
-
const data = useMemo(() => getDataFromRelatedFeatures({
|
|
9293
|
-
t,
|
|
9294
|
-
config: elementConfig,
|
|
9295
|
-
filters: currentPage?.filters,
|
|
9296
|
-
dataSource: dataSources?.find(({ name }) => name === relatedDataSource),
|
|
9297
|
-
layerInfo,
|
|
9298
|
-
relatedConfig: {
|
|
9299
|
-
attributeName: valueElement?.attributeName,
|
|
9300
|
-
attributeTitle: aliasElement?.attributeName,
|
|
9301
|
-
filterName,
|
|
9302
|
-
},
|
|
9303
|
-
}), [
|
|
9304
|
-
aliasElement?.attributeName,
|
|
9305
|
-
currentPage?.filters,
|
|
9306
|
-
dataSources,
|
|
9307
|
-
elementConfig,
|
|
9308
|
-
filterName,
|
|
9309
|
-
layerInfo,
|
|
9310
|
-
relatedDataSource,
|
|
9311
|
-
t,
|
|
9312
|
-
valueElement?.attributeName,
|
|
9313
|
-
]);
|
|
9314
|
-
const { hasAnyFilter, isFiltered, onFilter } = useWidgetFilters(type, filterName, data);
|
|
9315
|
-
const render = useMemo(() => getRenderElement({
|
|
9316
|
-
config,
|
|
9317
|
-
elementConfig,
|
|
9318
|
-
attributes,
|
|
9319
|
-
layerInfo,
|
|
9320
|
-
expandedContainers,
|
|
9321
|
-
selectedTabId,
|
|
9322
|
-
setSelectedTabId,
|
|
9323
|
-
pageIndex,
|
|
9324
|
-
type,
|
|
9325
|
-
}), [config, elementConfig, attributes, layerInfo, expandedContainers, selectedTabId, setSelectedTabId, pageIndex, type]);
|
|
9326
|
-
if (!InnerContainer) {
|
|
9327
|
-
return null;
|
|
9328
|
-
}
|
|
9329
|
-
return (jsx(InnerContainerWrapper, { hasAnyFilter: hasAnyFilter, isFiltered: isFiltered(value), column: column === undefined || column, onClick: filterName ? () => onFilter(value) : undefined, children: jsx(InnerContainer, { config: config, elementConfig: innerConfig, feature: feature, maxValue: maxValue, type: type, index: index, renderElement: render }) }));
|
|
9330
|
-
});
|
|
9331
|
-
|
|
9332
|
-
const DataSourceProgressContainerWrapper = styled.div.withConfig({ displayName: "DataSourceProgressContainerWrapper", componentId: "sc-d525nv" }) `
|
|
9333
|
-
width: 100%;
|
|
9334
|
-
|
|
9335
|
-
${sizeCssMixin};
|
|
9073
|
+
const ChartLegendColor = styled.div.withConfig({ displayName: "ChartLegendColor", componentId: "sc-zxnnio" }) `
|
|
9074
|
+
width: 0.5rem;
|
|
9075
|
+
height: 0.5rem;
|
|
9076
|
+
margin-right: 0.375rem;
|
|
9077
|
+
background-color: ${({ color }) => color};
|
|
9078
|
+
border-radius: ${({ theme: { borderRadius } }) => borderRadius.tiny};
|
|
9336
9079
|
`;
|
|
9337
|
-
const
|
|
9338
|
-
|
|
9080
|
+
const ChartLegendName = styled.div.withConfig({ displayName: "ChartLegendName", componentId: "sc-isz576" }) `
|
|
9081
|
+
flex: 1;
|
|
9082
|
+
font-size: ${({ $fontSize }) => $fontSize || "0.625rem"};
|
|
9083
|
+
color: ${({ theme: { palette }, $fontColor }) => $fontColor || palette.textPrimary};
|
|
9339
9084
|
`;
|
|
9340
9085
|
|
|
9341
|
-
|
|
9086
|
+
const ContainerAlias = styled(Flex).withConfig({ displayName: "ContainerAlias", componentId: "sc-1ftaxyu" }) `
|
|
9087
|
+
align-items: center;
|
|
9088
|
+
flex-wrap: nowrap;
|
|
9089
|
+
font-size: 0.75rem;
|
|
9090
|
+
color: ${({ theme: { palette } }) => palette.textSecondary};
|
|
9091
|
+
|
|
9092
|
+
&&& {
|
|
9093
|
+
margin-bottom: ${({ hasBottomMargin }) => (hasBottomMargin ? 0.25 : 0)}rem;
|
|
9094
|
+
}
|
|
9095
|
+
|
|
9096
|
+
span[kind] {
|
|
9097
|
+
margin-right: 0.5rem;
|
|
9098
|
+
|
|
9099
|
+
:after {
|
|
9100
|
+
color: ${({ theme: { palette } }) => palette.primary};
|
|
9101
|
+
}
|
|
9102
|
+
}
|
|
9103
|
+
`;
|
|
9104
|
+
const ContainerAliasIcon = styled.div.withConfig({ displayName: "ContainerAliasIcon", componentId: "sc-elezmi" }) `
|
|
9105
|
+
margin-right: 0.5rem;
|
|
9106
|
+
`;
|
|
9107
|
+
const ContainerChart = styled(Flex).withConfig({ displayName: "ContainerChart", componentId: "sc-16sh3wn" }) `
|
|
9108
|
+
justify-content: flex-start;
|
|
9109
|
+
|
|
9110
|
+
> * {
|
|
9111
|
+
display: flex;
|
|
9112
|
+
justify-content: center;
|
|
9113
|
+
width: 100%;
|
|
9114
|
+
}
|
|
9115
|
+
|
|
9116
|
+
/* fill: слот забирает остаток высоты ячейки (под слотами title/alias/legend) и центрирует тело графика. */
|
|
9117
|
+
${({ $fill }) => $fill &&
|
|
9118
|
+
css `
|
|
9119
|
+
flex: 1 1 auto;
|
|
9120
|
+
min-height: 0;
|
|
9121
|
+
align-items: center;
|
|
9122
|
+
justify-content: center;
|
|
9123
|
+
|
|
9124
|
+
> * {
|
|
9125
|
+
height: 100%;
|
|
9126
|
+
}
|
|
9127
|
+
`}
|
|
9128
|
+
`;
|
|
9129
|
+
const ContainerLegend = styled(Flex).withConfig({ displayName: "ContainerLegend", componentId: "sc-z1n1s8" }) ``;
|
|
9130
|
+
const ContainerUnits = styled.div.withConfig({ displayName: "ContainerUnits", componentId: "sc-1vurgy9" }) `
|
|
9131
|
+
margin-left: 0.5rem;
|
|
9132
|
+
white-space: nowrap;
|
|
9133
|
+
font-size: 0.75rem;
|
|
9134
|
+
`;
|
|
9135
|
+
const ContainerValue = styled(Flex).withConfig({ displayName: "ContainerValue", componentId: "sc-aj1rs9" }) `
|
|
9136
|
+
justify-content: flex-end;
|
|
9137
|
+
align-items: center;
|
|
9138
|
+
flex-wrap: nowrap;
|
|
9139
|
+
width: 100%;
|
|
9140
|
+
font-size: ${({ big }) => (big ? 1.5 : 1)}rem;
|
|
9141
|
+
color: ${({ fontColor, theme: { palette } }) => fontColor || palette.textPrimary};
|
|
9142
|
+
|
|
9143
|
+
/* fill: строка со слотом chart+legend забирает остаток высоты Container'а (гейт — только у Chart-контейнера). */
|
|
9144
|
+
${({ $fill }) => $fill &&
|
|
9145
|
+
css `
|
|
9146
|
+
flex: 1 1 auto;
|
|
9147
|
+
min-height: 0;
|
|
9148
|
+
`}
|
|
9149
|
+
|
|
9150
|
+
> * {
|
|
9151
|
+
width: ${({ column }) => (column ? "100%" : "auto")};
|
|
9152
|
+
}
|
|
9153
|
+
|
|
9154
|
+
${ContainerChart}, ${ContainerLegend} {
|
|
9155
|
+
width: ${({ column }) => (column ? "100%" : "50%")};
|
|
9156
|
+
}
|
|
9157
|
+
|
|
9158
|
+
${ContainerLegend} {
|
|
9159
|
+
margin-left: ${({ column }) => (column ? 0 : "1rem")};
|
|
9160
|
+
}
|
|
9161
|
+
|
|
9162
|
+
${ChartLegendContainer} {
|
|
9163
|
+
flex-direction: ${({ column }) => (column ? "row" : "column")};
|
|
9164
|
+
margin-top: ${({ column }) => (column ? "1rem" : 0)};
|
|
9165
|
+
}
|
|
9166
|
+
`;
|
|
9167
|
+
const ColorIconMixin = css `
|
|
9168
|
+
:after {
|
|
9169
|
+
color: ${({ $fontColor }) => $fontColor} !important;
|
|
9170
|
+
}
|
|
9171
|
+
`;
|
|
9172
|
+
const SizeIconMixin = css `
|
|
9173
|
+
:after {
|
|
9174
|
+
font-size: ${({ $fontSize }) => $fontSize}px !important;
|
|
9175
|
+
}
|
|
9176
|
+
`;
|
|
9177
|
+
styled(Icon).withConfig({ displayName: "ContainerIcon", componentId: "sc-1w9w0bq" }) `
|
|
9178
|
+
width: auto;
|
|
9179
|
+
height: auto;
|
|
9180
|
+
margin-bottom: 0.5rem;
|
|
9181
|
+
${({ $fontColor }) => !!$fontColor && ColorIconMixin};
|
|
9182
|
+
${({ $fontSize }) => !!$fontSize && SizeIconMixin};
|
|
9183
|
+
`;
|
|
9184
|
+
const SvgContainerColorMixin$1 = css `
|
|
9185
|
+
path,
|
|
9186
|
+
line,
|
|
9187
|
+
circle {
|
|
9188
|
+
fill: ${({ $fontColor }) => $fontColor};
|
|
9189
|
+
}
|
|
9190
|
+
`;
|
|
9191
|
+
const SvgContainer$1 = styled.div.withConfig({ displayName: "SvgContainer", componentId: "sc-1qs5gmx" }) `
|
|
9192
|
+
&&& {
|
|
9193
|
+
min-width: ${({ $width }) => ($width ? `${$width}px` : "1rem")};
|
|
9194
|
+
max-width: ${({ $width }) => ($width ? `${$width}px` : "1rem")};
|
|
9195
|
+
|
|
9196
|
+
${({ $fontColor }) => !!$fontColor && SvgContainerColorMixin$1};
|
|
9197
|
+
|
|
9198
|
+
> * {
|
|
9199
|
+
min-width: inherit;
|
|
9200
|
+
}
|
|
9201
|
+
}
|
|
9202
|
+
`;
|
|
9203
|
+
const TwoColumnContainerWrapper = styled(Container).withConfig({ displayName: "TwoColumnContainerWrapper", componentId: "sc-1ogv4d3" }) `
|
|
9204
|
+
flex-direction: row;
|
|
9205
|
+
flex-wrap: nowrap;
|
|
9206
|
+
align-items: center;
|
|
9207
|
+
|
|
9208
|
+
> * {
|
|
9209
|
+
flex: 1;
|
|
9210
|
+
}
|
|
9211
|
+
|
|
9212
|
+
> ${ContainerValue} {
|
|
9213
|
+
justify-content: flex-end;
|
|
9214
|
+
|
|
9215
|
+
> * {
|
|
9216
|
+
text-align: right;
|
|
9217
|
+
}
|
|
9218
|
+
}
|
|
9219
|
+
`;
|
|
9220
|
+
|
|
9221
|
+
const CameraContainer = memo(({ elementConfig, type, renderElement }) => {
|
|
9222
|
+
const { expandedContainers } = useWidgetContext(type);
|
|
9223
|
+
const { id, options } = elementConfig || {};
|
|
9224
|
+
const { expandable, expanded } = options || {};
|
|
9225
|
+
const isVisible = isVisibleContainer(id, expandable, expanded, expandedContainers);
|
|
9226
|
+
const { root, body } = useContainerRoot({ elementConfig });
|
|
9227
|
+
return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsxs(Container, { ...body, children: [jsx(ContainerAlias, { hasBottomMargin: true, children: renderElement({ id: "alias" }) }), jsx(ContainerValue, { children: renderElement({ id: "value", wrap: false }) })] }))] }));
|
|
9228
|
+
});
|
|
9229
|
+
|
|
9230
|
+
/**
|
|
9231
|
+
* Контекст вписывания графика (`options.fill` контейнера Chart).
|
|
9232
|
+
*
|
|
9233
|
+
* Компонент `Chart` рендерится через `renderElement({ id: "chart" })` и получает только дочерний
|
|
9234
|
+
* узел `{ id: "chart" }` — опции контейнера до него не доходят. `ChartContainer` оборачивает
|
|
9235
|
+
* график в провайдер этого контекста, а `Chart` читает значение через `useContext`.
|
|
9236
|
+
*/
|
|
9237
|
+
const FillContext = createContext({ fill: false, fitHeight: false });
|
|
9238
|
+
|
|
9239
|
+
const ChartContainer = memo(({ elementConfig, isVisible, type, renderElement }) => {
|
|
9240
|
+
const { options, children } = elementConfig || {};
|
|
9241
|
+
const { twoColumns, hideEmpty, height } = options || {};
|
|
9242
|
+
const fill = options?.fill;
|
|
9243
|
+
const fillContextValue = useMemo(() => ({ fill: !!fill, fitHeight: !!fill && height != null }), [fill, height]);
|
|
9244
|
+
const aliasElement = children.find(child => child.id === "alias");
|
|
9245
|
+
const chartElement = children.find(child => child.id === "chart");
|
|
9246
|
+
const legendElement = children.find(child => child.id === "legend");
|
|
9247
|
+
const { root, body } = useContainerRoot({ elementConfig });
|
|
9248
|
+
const { data, loading } = useChartData({
|
|
9249
|
+
element: chartElement,
|
|
9250
|
+
type,
|
|
9251
|
+
});
|
|
9252
|
+
if (data[0] && !data[0].items) {
|
|
9253
|
+
return jsx(DataSourceError, { name: elementConfig.templateName });
|
|
9254
|
+
}
|
|
9255
|
+
const hasItems = !!data[0]?.items?.length;
|
|
9256
|
+
if (!loading && !hasItems && hideEmpty)
|
|
9257
|
+
return null;
|
|
9258
|
+
return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsxs(Container, { ...body, isColumn: true, children: [aliasElement && jsx(ContainerAlias, { hasBottomMargin: true, children: renderElement({ id: "alias" }) }), jsx(ContainerValue, { column: !twoColumns, alignItems: twoColumns && fill ? "stretch" : "center", "$fill": !!fill, children: hasItems ? (jsxs(Fragment$1, { children: [jsx(ContainerChart, { "$fill": !!fill, children: jsx(FillContext.Provider, { value: fillContextValue, children: renderElement({ id: "chart" }) }) }), jsx(ContainerLegend, { justifyContent: legendElement?.options?.center ? "center" : "flex-start", children: renderElement({ id: "legend" }) })] })) : (jsx(Fragment$1, { children: "\u2014" })) })] }))] }));
|
|
9259
|
+
});
|
|
9260
|
+
|
|
9261
|
+
const InnerContainerWrapper = styled.div.withConfig({ displayName: "InnerContainerWrapper", componentId: "sc-1a1fl7q" }) `
|
|
9262
|
+
cursor: ${({ onClick }) => (onClick ? "pointer" : "default")};
|
|
9263
|
+
width: ${({ column }) => (column ? "100%" : "auto")};
|
|
9264
|
+
opacity: ${({ hasAnyFilter, isFiltered }) => (isFiltered ? 1 : hasAnyFilter ? FILTERED_VALUE_OPACITY / 100 : 1)};
|
|
9265
|
+
|
|
9266
|
+
/*
|
|
9267
|
+
В ряду обёртка не сжимается. Родительский Container снимает порог по контенту
|
|
9268
|
+
(min-width: 0 у детей), из-за чего обёртка ужималась до доли ширины ряда, а внутренний
|
|
9269
|
+
шаблон — например, плашка RoundedBackground с жёсткими 9rem — блочный и не сжимается
|
|
9270
|
+
вместе с ней. Записи вылезали за свои обёртки и наезжали друг на друга.
|
|
9271
|
+
*/
|
|
9272
|
+
${({ column }) => !column && "flex-shrink: 0;"}
|
|
9273
|
+
`;
|
|
9274
|
+
|
|
9275
|
+
const DataSourceInnerContainer = memo(({ config, elementConfig, feature, maxValue, type, index, innerComponent }) => {
|
|
9276
|
+
const { t } = useGlobalContext();
|
|
9277
|
+
const { expandedContainers, selectedTabId, setSelectedTabId, dataSources } = useWidgetContext(type);
|
|
9278
|
+
const { pageIndex, currentPage } = useWidgetPage(type);
|
|
9279
|
+
const { attributes, layerInfo } = useRelatedDataSourceAttributes({
|
|
9280
|
+
type,
|
|
9281
|
+
elementConfig,
|
|
9282
|
+
dataSources,
|
|
9283
|
+
feature,
|
|
9284
|
+
});
|
|
9285
|
+
const { options, children } = elementConfig || {};
|
|
9286
|
+
const { relatedDataSource, filterName, column } = options || {};
|
|
9287
|
+
/**
|
|
9288
|
+
* Конфиг записи — тот же узел хоста, но без слота фона.
|
|
9289
|
+
*
|
|
9290
|
+
* У `DataSource`/`DataSourceProgress` дети — слоты ВНУТРЕННЕГО шаблона, поэтому `bgImage`
|
|
9291
|
+
* достался бы и хосту, и каждой записи: одна картинка нарисовалась бы дважды. Фон остаётся
|
|
9292
|
+
* за хостом (он и есть контейнер), а записи получают конфиг без слота.
|
|
9293
|
+
*/
|
|
9294
|
+
const innerConfig = useMemo(() => elementConfig && children?.some(({ id }) => id === BG_IMAGE_SLOT_ID)
|
|
9295
|
+
? { ...elementConfig, children: children.filter(({ id }) => id !== BG_IMAGE_SLOT_ID) }
|
|
9296
|
+
: elementConfig, [elementConfig, children]);
|
|
9297
|
+
const aliasElement = useMemo(() => children?.find(({ id }) => id === "alias"), [children]);
|
|
9298
|
+
const valueElement = useMemo(() => children?.find(({ id }) => id === "value"), [children]);
|
|
9299
|
+
const aliasAttribute = useMemo(() => (aliasElement ? attributes?.find(({ attributeName }) => attributeName === aliasElement.attributeName) : null), [aliasElement, attributes]);
|
|
9300
|
+
const value = aliasAttribute?.value;
|
|
9301
|
+
const InnerContainer = innerComponent;
|
|
9302
|
+
const data = useMemo(() => getDataFromRelatedFeatures({
|
|
9303
|
+
t,
|
|
9304
|
+
config: elementConfig,
|
|
9305
|
+
filters: currentPage?.filters,
|
|
9306
|
+
dataSource: dataSources?.find(({ name }) => name === relatedDataSource),
|
|
9307
|
+
layerInfo,
|
|
9308
|
+
relatedConfig: {
|
|
9309
|
+
attributeName: valueElement?.attributeName,
|
|
9310
|
+
attributeTitle: aliasElement?.attributeName,
|
|
9311
|
+
filterName,
|
|
9312
|
+
},
|
|
9313
|
+
}), [
|
|
9314
|
+
aliasElement?.attributeName,
|
|
9315
|
+
currentPage?.filters,
|
|
9316
|
+
dataSources,
|
|
9317
|
+
elementConfig,
|
|
9318
|
+
filterName,
|
|
9319
|
+
layerInfo,
|
|
9320
|
+
relatedDataSource,
|
|
9321
|
+
t,
|
|
9322
|
+
valueElement?.attributeName,
|
|
9323
|
+
]);
|
|
9324
|
+
const { hasAnyFilter, isFiltered, onFilter } = useWidgetFilters(type, filterName, data);
|
|
9325
|
+
const render = useMemo(() => getRenderElement({
|
|
9326
|
+
config,
|
|
9327
|
+
elementConfig,
|
|
9328
|
+
attributes,
|
|
9329
|
+
layerInfo,
|
|
9330
|
+
expandedContainers,
|
|
9331
|
+
selectedTabId,
|
|
9332
|
+
setSelectedTabId,
|
|
9333
|
+
pageIndex,
|
|
9334
|
+
type,
|
|
9335
|
+
}), [config, elementConfig, attributes, layerInfo, expandedContainers, selectedTabId, setSelectedTabId, pageIndex, type]);
|
|
9336
|
+
if (!InnerContainer) {
|
|
9337
|
+
return null;
|
|
9338
|
+
}
|
|
9339
|
+
return (jsx(InnerContainerWrapper, { hasAnyFilter: hasAnyFilter, isFiltered: isFiltered(value), column: column === undefined || column, onClick: filterName ? () => onFilter(value) : undefined, children: jsx(InnerContainer, { config: config, elementConfig: innerConfig, feature: feature, maxValue: maxValue, type: type, index: index, renderElement: render }) }));
|
|
9340
|
+
});
|
|
9341
|
+
|
|
9342
|
+
const DataSourceProgressContainerWrapper = styled.div.withConfig({ displayName: "DataSourceProgressContainerWrapper", componentId: "sc-d525nv" }) `
|
|
9343
|
+
width: 100%;
|
|
9344
|
+
|
|
9345
|
+
${sizeCssMixin};
|
|
9346
|
+
`;
|
|
9347
|
+
const ContainerToggler = styled(LegendToggler).withConfig({ displayName: "ContainerToggler", componentId: "sc-pgbop" }) `
|
|
9348
|
+
width: auto;
|
|
9349
|
+
`;
|
|
9350
|
+
|
|
9351
|
+
/** Дефолтный внутренний отступ между плитками, px (ТЗ §11). */
|
|
9342
9352
|
const DEFAULT_TILE_GAP = 8;
|
|
9343
9353
|
/** Раскладка ряда плиток (grid `justify-content`) для не-`stretch` выравнивания. */
|
|
9344
9354
|
const ROW_JUSTIFY = {
|
|
@@ -10855,6 +10865,61 @@ const LayersContainer = memo(({ type, elementConfig, renderElement }) => {
|
|
|
10855
10865
|
return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsx(LayersContainerWrapper, { ...body, children: jsx(LayerTree, { layers: layers, onlyMainTools: true }) }))] }));
|
|
10856
10866
|
});
|
|
10857
10867
|
|
|
10868
|
+
const useRenderContainer = ({ elementConfig, type, renderElement, renderBody, }) => {
|
|
10869
|
+
const { attributes } = useWidgetContext(type);
|
|
10870
|
+
const { getRenderContainerItem, attributesToRender } = useContainerAttributes({
|
|
10871
|
+
elementConfig,
|
|
10872
|
+
type,
|
|
10873
|
+
renderElement,
|
|
10874
|
+
});
|
|
10875
|
+
const renderContainer = useCallback((attribute) => {
|
|
10876
|
+
const item = getRenderContainerItem(elementConfig, attribute);
|
|
10877
|
+
const itemAttribute = attribute
|
|
10878
|
+
? attributes?.find(({ attributeName }) => attributeName === attribute)
|
|
10879
|
+
: undefined;
|
|
10880
|
+
const overrideTemplateName = getDisplayTemplateNameFromAttribute(itemAttribute);
|
|
10881
|
+
if (overrideTemplateName) {
|
|
10882
|
+
const OverrideContainer = getContainerComponent(overrideTemplateName);
|
|
10883
|
+
if (OverrideContainer) {
|
|
10884
|
+
const itemConfig = {
|
|
10885
|
+
...elementConfig,
|
|
10886
|
+
children: (elementConfig?.children ?? []).map(child => ({
|
|
10887
|
+
...child,
|
|
10888
|
+
attributeName: attribute,
|
|
10889
|
+
})),
|
|
10890
|
+
templateName: overrideTemplateName,
|
|
10891
|
+
};
|
|
10892
|
+
return (jsx(OverrideContainer, { type: type, elementConfig: itemConfig, renderElement: item.render }, attribute));
|
|
10893
|
+
}
|
|
10894
|
+
}
|
|
10895
|
+
if (isEmptyElementValue(item.value) && item.hideEmpty)
|
|
10896
|
+
return null;
|
|
10897
|
+
return renderBody(item, attribute);
|
|
10898
|
+
}, [getRenderContainerItem, elementConfig, attributes, type, renderBody]);
|
|
10899
|
+
return { renderContainer, attributesToRender };
|
|
10900
|
+
};
|
|
10901
|
+
|
|
10902
|
+
const OneColumnContainer = memo(({ type, elementConfig, renderElement }) => {
|
|
10903
|
+
const { width, height, overflow } = elementConfig?.options || {};
|
|
10904
|
+
const templateName = elementConfig?.templateName;
|
|
10905
|
+
const bgHost = useBgImageHost(elementConfig);
|
|
10906
|
+
const renderBody = useCallback(({ id, value, style, hasUnits, render }) => (jsxs(Container, { id: id, isColumn: true, "data-templatename": templateName, style: style, "$sizeCss": getWrapperSizeStyle({
|
|
10907
|
+
style,
|
|
10908
|
+
width,
|
|
10909
|
+
height,
|
|
10910
|
+
overflow,
|
|
10911
|
+
defaults: BASE_CONTAINER_STYLE,
|
|
10912
|
+
defaultWidth: FILL_SIZE,
|
|
10913
|
+
}), ...bgHost, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: render }), jsxs(ContainerAlias, { hasBottomMargin: true, children: [render({ id: "alias" }), render({ id: "tooltip" }), render({ id: "modal" })] }), jsxs(ContainerValue, { children: [value, hasUnits && (jsx(ContainerUnits, { children: render({ id: "units" }) }))] })] })), [width, height, overflow, templateName, bgHost, elementConfig]);
|
|
10914
|
+
const { renderContainer, attributesToRender } = useRenderContainer({
|
|
10915
|
+
type,
|
|
10916
|
+
elementConfig,
|
|
10917
|
+
renderElement,
|
|
10918
|
+
renderBody,
|
|
10919
|
+
});
|
|
10920
|
+
return attributesToRender ? (jsx(Fragment$1, { children: attributesToRender.map(renderContainer) })) : (renderContainer());
|
|
10921
|
+
});
|
|
10922
|
+
|
|
10858
10923
|
const PagesContainer = memo(({ type = WidgetType.Dashboard, noBorders }) => {
|
|
10859
10924
|
const { config } = useWidgetConfig(type);
|
|
10860
10925
|
const { pageIndex, currentPage } = useWidgetPage(type);
|
|
@@ -12023,6 +12088,734 @@ const UploadContainer = memo(({ type, elementConfig, renderElement }) => {
|
|
|
12023
12088
|
return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsx(UploadContainerWrapper, { ...body, children: renderElement({ id: "uploader", wrap: false }) }))] }));
|
|
12024
12089
|
});
|
|
12025
12090
|
|
|
12091
|
+
/** Зелёный для плашки «Ваш ответ принят». */
|
|
12092
|
+
const ACCEPTED_GREEN = "#2fa84f";
|
|
12093
|
+
const VoteWrapper = styled(Flex).withConfig({ displayName: "VoteWrapper", componentId: "sc-1cvc2lm" }) `
|
|
12094
|
+
flex-direction: column;
|
|
12095
|
+
gap: 0.75rem;
|
|
12096
|
+
width: 100%;
|
|
12097
|
+
color: ${({ theme: { palette } }) => palette.textPrimary};
|
|
12098
|
+
`;
|
|
12099
|
+
const VoteHeading = styled.div.withConfig({ displayName: "VoteHeading", componentId: "sc-rzdt4p" }) `
|
|
12100
|
+
font-size: 1rem;
|
|
12101
|
+
font-weight: 600;
|
|
12102
|
+
color: ${({ theme: { palette } }) => palette.textPrimary};
|
|
12103
|
+
`;
|
|
12104
|
+
const VoteQuestionText = styled.div.withConfig({ displayName: "VoteQuestionText", componentId: "sc-1t4w892" }) `
|
|
12105
|
+
font-size: 0.9375rem;
|
|
12106
|
+
font-weight: 600;
|
|
12107
|
+
color: ${({ theme: { palette } }) => palette.textPrimary};
|
|
12108
|
+
`;
|
|
12109
|
+
const VoteCategoryLabel = styled.div.withConfig({ displayName: "VoteCategoryLabel", componentId: "sc-z1fkgy" }) `
|
|
12110
|
+
font-size: 0.75rem;
|
|
12111
|
+
color: ${({ theme: { palette } }) => palette.textSecondary};
|
|
12112
|
+
`;
|
|
12113
|
+
const VoteSectionLabel = styled.div.withConfig({ displayName: "VoteSectionLabel", componentId: "sc-1xxrikh" }) `
|
|
12114
|
+
font-size: 0.75rem;
|
|
12115
|
+
color: ${({ theme: { palette } }) => palette.textSecondary};
|
|
12116
|
+
`;
|
|
12117
|
+
const VoteChipList = styled(Flex).withConfig({ displayName: "VoteChipList", componentId: "sc-1ahupix" }) `
|
|
12118
|
+
flex-wrap: wrap;
|
|
12119
|
+
gap: 0.375rem;
|
|
12120
|
+
`;
|
|
12121
|
+
const VoteChip = styled(Flex).withConfig({ displayName: "VoteChip", componentId: "sc-x1icg3" }) `
|
|
12122
|
+
align-items: center;
|
|
12123
|
+
gap: 0.25rem;
|
|
12124
|
+
padding: 0.25rem 0.5rem;
|
|
12125
|
+
background-color: ${({ theme: { palette } }) => palette.elementDark};
|
|
12126
|
+
border-radius: 0.25rem;
|
|
12127
|
+
font-size: 0.8125rem;
|
|
12128
|
+
|
|
12129
|
+
:hover {
|
|
12130
|
+
background-color: ${({ theme: { palette } }) => palette.elementDeep};
|
|
12131
|
+
}
|
|
12132
|
+
`;
|
|
12133
|
+
/**
|
|
12134
|
+
* Список вариантов ответа.
|
|
12135
|
+
*
|
|
12136
|
+
* Обёртка — именно `RadioGroup`: `Radio` из uilib читает его legacy-контекст и без него бросает
|
|
12137
|
+
* `Radio must be controlled with RadioGroup context.` — даже когда `checked` и `onChange` заданы
|
|
12138
|
+
* на самом контроле. Группа стоит всегда, а не только в режиме одиночного выбора: лишнего она не
|
|
12139
|
+
* рисует, зато контекст не может пропасть при смене экрана.
|
|
12140
|
+
*
|
|
12141
|
+
* Подпись у `Radio` своя, но текст варианта рисует строка — он делит место со счётчиком и
|
|
12142
|
+
* прогресс-баром. Поэтому пустой узел подписи и отступ кружка до него снимаются.
|
|
12143
|
+
*/
|
|
12144
|
+
const VoteVariantList = styled(RadioGroup).withConfig({ displayName: "VoteVariantList", componentId: "sc-kie7qv" }) `
|
|
12145
|
+
width: 100%;
|
|
12146
|
+
|
|
12147
|
+
label > div {
|
|
12148
|
+
display: none;
|
|
12149
|
+
}
|
|
12150
|
+
|
|
12151
|
+
label > span {
|
|
12152
|
+
margin-right: 0;
|
|
12153
|
+
}
|
|
12154
|
+
`;
|
|
12155
|
+
const VoteVariantRow = styled.div.withConfig({ displayName: "VoteVariantRow", componentId: "sc-hlij7m" }) `
|
|
12156
|
+
display: flex;
|
|
12157
|
+
flex-direction: column;
|
|
12158
|
+
gap: 0.25rem;
|
|
12159
|
+
padding: 0.375rem 0;
|
|
12160
|
+
`;
|
|
12161
|
+
const VoteVariantHead = styled(Flex).withConfig({ displayName: "VoteVariantHead", componentId: "sc-1kbw26l" }) `
|
|
12162
|
+
align-items: center;
|
|
12163
|
+
justify-content: space-between;
|
|
12164
|
+
gap: 0.5rem;
|
|
12165
|
+
font-size: 0.875rem;
|
|
12166
|
+
`;
|
|
12167
|
+
const VoteVariantText = styled.span.withConfig({ displayName: "VoteVariantText", componentId: "sc-1el70dv" }) `
|
|
12168
|
+
flex: 1;
|
|
12169
|
+
min-width: 0;
|
|
12170
|
+
`;
|
|
12171
|
+
const VoteVariantCount = styled.span.withConfig({ displayName: "VoteVariantCount", componentId: "sc-jle8ey" }) `
|
|
12172
|
+
flex-shrink: 0;
|
|
12173
|
+
font-size: 0.8125rem;
|
|
12174
|
+
color: ${({ theme: { palette } }) => palette.textSecondary};
|
|
12175
|
+
`;
|
|
12176
|
+
const VoteAcceptedBadge = styled(Flex).withConfig({ displayName: "VoteAcceptedBadge", componentId: "sc-1utdte8" }) `
|
|
12177
|
+
align-items: center;
|
|
12178
|
+
justify-content: center;
|
|
12179
|
+
gap: 0.5rem;
|
|
12180
|
+
padding: 0.5rem 0.75rem;
|
|
12181
|
+
border-radius: 0.375rem;
|
|
12182
|
+
background-color: ${ACCEPTED_GREEN};
|
|
12183
|
+
color: #ffffff;
|
|
12184
|
+
font-size: 0.875rem;
|
|
12185
|
+
`;
|
|
12186
|
+
const VoteAuthHint = styled.div.withConfig({ displayName: "VoteAuthHint", componentId: "sc-l6fdf1" }) `
|
|
12187
|
+
font-size: 0.8125rem;
|
|
12188
|
+
color: ${({ theme: { palette } }) => palette.textSecondary};
|
|
12189
|
+
`;
|
|
12190
|
+
const VoteEditLink = styled.button.withConfig({ displayName: "VoteEditLink", componentId: "sc-1iedcup" }) `
|
|
12191
|
+
align-self: flex-start;
|
|
12192
|
+
padding: 0;
|
|
12193
|
+
border: none;
|
|
12194
|
+
background: none;
|
|
12195
|
+
cursor: pointer;
|
|
12196
|
+
font-size: 0.8125rem;
|
|
12197
|
+
color: ${({ theme: { palette } }) => palette.primary};
|
|
12198
|
+
|
|
12199
|
+
:hover {
|
|
12200
|
+
text-decoration: underline;
|
|
12201
|
+
}
|
|
12202
|
+
`;
|
|
12203
|
+
const VoteShareBlock = styled(Flex).withConfig({ displayName: "VoteShareBlock", componentId: "sc-a8sttg" }) `
|
|
12204
|
+
flex-direction: column;
|
|
12205
|
+
gap: 0.375rem;
|
|
12206
|
+
margin-top: 0.25rem;
|
|
12207
|
+
`;
|
|
12208
|
+
const VoteShareChips = styled(Flex).withConfig({ displayName: "VoteShareChips", componentId: "sc-1xnysrh" }) `
|
|
12209
|
+
flex-wrap: wrap;
|
|
12210
|
+
gap: 0.375rem;
|
|
12211
|
+
`;
|
|
12212
|
+
const VoteShareChip = styled.div.withConfig({ displayName: "VoteShareChip", componentId: "sc-mrpbcg" }) `
|
|
12213
|
+
padding: 0.25rem 0.625rem;
|
|
12214
|
+
border-radius: 1rem;
|
|
12215
|
+
background-color: ${({ theme: { palette } }) => palette.element};
|
|
12216
|
+
color: ${({ theme: { palette } }) => palette.textSecondary};
|
|
12217
|
+
font-size: 0.75rem;
|
|
12218
|
+
`;
|
|
12219
|
+
styled.div.withConfig({ displayName: "VoteError", componentId: "sc-1gfzwqo" }) `
|
|
12220
|
+
font-size: 0.8125rem;
|
|
12221
|
+
color: ${({ theme: { palette } }) => palette.error};
|
|
12222
|
+
`;
|
|
12223
|
+
|
|
12224
|
+
/** Имена полей таблиц голосования. */
|
|
12225
|
+
const VOTE_FIELDS = {
|
|
12226
|
+
category: {
|
|
12227
|
+
id: "id",
|
|
12228
|
+
name: "name",
|
|
12229
|
+
},
|
|
12230
|
+
question: {
|
|
12231
|
+
id: "id",
|
|
12232
|
+
text: "text",
|
|
12233
|
+
categoryId: "category_id",
|
|
12234
|
+
userName: "user_name",
|
|
12235
|
+
multiSelect: "multi_select",
|
|
12236
|
+
},
|
|
12237
|
+
variant: {
|
|
12238
|
+
id: "id",
|
|
12239
|
+
questionId: "question_id",
|
|
12240
|
+
text: "text",
|
|
12241
|
+
},
|
|
12242
|
+
answer: {
|
|
12243
|
+
id: "id",
|
|
12244
|
+
questionId: "question_id",
|
|
12245
|
+
userName: "user_name",
|
|
12246
|
+
variantId: "variant_id",
|
|
12247
|
+
},
|
|
12248
|
+
};
|
|
12249
|
+
/** Минимальное число вариантов ответа в голосовании. */
|
|
12250
|
+
const MIN_VARIANTS = 2;
|
|
12251
|
+
/** Лимит при чтении списков (вариантов/ответов/категорий). */
|
|
12252
|
+
const VOTE_FETCH_LIMIT = 10000;
|
|
12253
|
+
/** База для перевода доли голосов в проценты. */
|
|
12254
|
+
const PERCENT_BASE = 100;
|
|
12255
|
+
/** Namespace переводов потребляющего приложения (подписи берутся через `t` с defaultValue). */
|
|
12256
|
+
const VOTE_NS = "dashboard";
|
|
12257
|
+
/**
|
|
12258
|
+
* Имя публичного (анонимного) аккаунта портала.
|
|
12259
|
+
*
|
|
12260
|
+
* Под ним сервер отдаёт `getUserInfo()` любому неавторизованному посетителю шаренного проекта,
|
|
12261
|
+
* поэтому «кто уже голосовал» по нему не считается: первый же голос закрыл бы голосование всем
|
|
12262
|
+
* сразу. Такой посетитель для контейнера — неавторизованный, ему доступны только результаты.
|
|
12263
|
+
*
|
|
12264
|
+
* Признака анонима в API нет (`UserInfoDc` — это `username` + `roles`), поэтому имя приходится
|
|
12265
|
+
* знать по конвенции портала: оно же стоит ролью в ACL публичных ресурсов.
|
|
12266
|
+
*/
|
|
12267
|
+
const PUBLIC_USER_NAME = "public_user";
|
|
12268
|
+
/** Социальные сети для блока «Поделиться». */
|
|
12269
|
+
const VOTE_SOCIALS = ["Telegram", "VK"];
|
|
12270
|
+
|
|
12271
|
+
/** Локальное состояние формы создания/редактирования голосования (категория, вопрос, чипсы-варианты). */
|
|
12272
|
+
const useVoteForm = (initialValues) => {
|
|
12273
|
+
const [categoryId, setCategoryId] = useState(initialValues?.categoryId ?? "");
|
|
12274
|
+
const [text, setText] = useState(initialValues?.text ?? "");
|
|
12275
|
+
const [multiSelect, setMultiSelect] = useState(initialValues?.multiSelect ?? false);
|
|
12276
|
+
const [variants, setVariants] = useState(initialValues?.variants ?? []);
|
|
12277
|
+
const [draft, setDraft] = useState("");
|
|
12278
|
+
const addVariant = useCallback(() => {
|
|
12279
|
+
const value = draft.trim();
|
|
12280
|
+
if (!value)
|
|
12281
|
+
return;
|
|
12282
|
+
setVariants(prev => [...prev, { text: value }]);
|
|
12283
|
+
setDraft("");
|
|
12284
|
+
}, [draft]);
|
|
12285
|
+
const removeVariant = useCallback((index) => {
|
|
12286
|
+
setVariants(prev => prev.filter((_, itemIndex) => itemIndex !== index));
|
|
12287
|
+
}, []);
|
|
12288
|
+
const isValid = useMemo(() => Boolean(categoryId && text.trim() && variants.length >= MIN_VARIANTS), [categoryId, text, variants.length]);
|
|
12289
|
+
const values = useMemo(() => ({ categoryId, text: text.trim(), multiSelect, variants }), [categoryId, text, multiSelect, variants]);
|
|
12290
|
+
return {
|
|
12291
|
+
categoryId,
|
|
12292
|
+
setCategoryId,
|
|
12293
|
+
text,
|
|
12294
|
+
setText,
|
|
12295
|
+
multiSelect,
|
|
12296
|
+
setMultiSelect,
|
|
12297
|
+
variants,
|
|
12298
|
+
addVariant,
|
|
12299
|
+
removeVariant,
|
|
12300
|
+
draft,
|
|
12301
|
+
setDraft,
|
|
12302
|
+
isValid,
|
|
12303
|
+
values,
|
|
12304
|
+
};
|
|
12305
|
+
};
|
|
12306
|
+
|
|
12307
|
+
const VoteCreateForm = ({ categories, initialValues, isEditing, canDelete, submitting, onSubmit, onDelete, onCancel, }) => {
|
|
12308
|
+
const { t } = useGlobalContext();
|
|
12309
|
+
const form = useVoteForm(initialValues);
|
|
12310
|
+
const { categoryId, setCategoryId, text, setText, multiSelect, setMultiSelect, variants, addVariant, removeVariant, draft, setDraft, isValid, values, } = form;
|
|
12311
|
+
const categoryOptions = useMemo(() => categories.map(({ id, name }) => ({ text: name, value: id })), [categories]);
|
|
12312
|
+
return (jsxs(VoteWrapper, { children: [!isEditing && jsx(VoteHeading, { children: t("vote.newVote", { ns: VOTE_NS, defaultValue: "Новое голосование" }) }), jsx(Dropdown, { zIndex: 110000, label: t("vote.category", { ns: VOTE_NS, defaultValue: "Категория" }), width: "100%", placeholder: t("vote.categoryPlaceholder", { ns: VOTE_NS, defaultValue: "Выберите категорию" }), options: categoryOptions, value: categoryId, onChange: items => setCategoryId(String(items[0]?.value ?? "")) }), jsx(Input, { label: t("vote.question", { ns: VOTE_NS, defaultValue: "Вопрос" }), width: "100%", placeholder: t("vote.questionPlaceholder", { ns: VOTE_NS, defaultValue: "Текст вопроса" }), value: text, onChange: ({ target }) => setText(target.value) }), jsx(VoteSectionLabel, { children: t("vote.variants", { ns: VOTE_NS, defaultValue: "Варианты ответов" }) }), !!variants.length && (jsx(VoteChipList, { children: variants.map(({ text: variantText }, index) => (jsxs(VoteChip, { children: [jsx(VoteVariantText, { children: variantText }), jsx(IconButton, { kind: "close", onClick: () => removeVariant(index) })] }, `${variantText}:${index}`))) })), jsx(Input, { width: "100%", placeholder: t("vote.variantPlaceholder", { ns: VOTE_NS, defaultValue: "Введите вариант и нажмите Enter" }), value: draft, onChange: ({ target }) => setDraft(target.value), onKeyDown: event => {
|
|
12313
|
+
if (event.key === "Enter") {
|
|
12314
|
+
event.preventDefault();
|
|
12315
|
+
addVariant();
|
|
12316
|
+
}
|
|
12317
|
+
} }), jsxs(Flex, { alignItems: "center", justifyContent: "space-between", children: [jsx(VoteSectionLabel, { children: t("vote.multiSelect", { ns: VOTE_NS, defaultValue: "Множественный выбор" }) }), jsx(Switch, { checked: multiSelect, onChange: () => setMultiSelect(prev => !prev) })] }), jsx(RaisedButton, { primary: true, disabled: !isValid || submitting, onClick: () => onSubmit(values), children: isEditing
|
|
12318
|
+
? t("vote.save", { ns: VOTE_NS, defaultValue: "Сохранить" })
|
|
12319
|
+
: t("vote.create", { ns: VOTE_NS, defaultValue: "Создать голосование" }) }), isEditing && (jsxs(Flex, { alignItems: "center", justifyContent: "space-between", children: [onCancel && (jsx(VoteEditLink, { onClick: onCancel, children: t("vote.cancel", { ns: VOTE_NS, defaultValue: "Отмена" }) })), canDelete && onDelete && (jsx(RaisedButton, { error: true, disabled: submitting, onClick: onDelete, children: t("vote.delete", { ns: VOTE_NS, defaultValue: "Удалить голосование" }) }))] }))] }));
|
|
12320
|
+
};
|
|
12321
|
+
|
|
12322
|
+
/** Блок «Поделиться в соцсетях» — пока только вёрстка, без рабочих ссылок. */
|
|
12323
|
+
const VoteShare = () => {
|
|
12324
|
+
const { t } = useGlobalContext();
|
|
12325
|
+
return (jsxs(VoteShareBlock, { children: [jsx(VoteSectionLabel, { children: t("vote.share", { ns: VOTE_NS, defaultValue: "Поделиться в соцсетях" }) }), jsx(VoteShareChips, { children: VOTE_SOCIALS.map(name => (jsx(VoteShareChip, { children: name }, name))) })] }));
|
|
12326
|
+
};
|
|
12327
|
+
|
|
12328
|
+
const VoteResults = ({ question, category, results, screen, isOwner, hasAnswers, submitting, onSubmit, onEdit, }) => {
|
|
12329
|
+
const { t } = useGlobalContext();
|
|
12330
|
+
const [selected, setSelected] = useState([]);
|
|
12331
|
+
const showControls = screen === "voting";
|
|
12332
|
+
const showShare = screen === "voting" || screen === "voted";
|
|
12333
|
+
const canEdit = screen === "voting" && isOwner && !hasAnswers;
|
|
12334
|
+
const toggle = useCallback((variantId) => {
|
|
12335
|
+
setSelected(prev => {
|
|
12336
|
+
if (!question.multiSelect)
|
|
12337
|
+
return [variantId];
|
|
12338
|
+
return prev.includes(variantId) ? prev.filter(id => id !== variantId) : [...prev, variantId];
|
|
12339
|
+
});
|
|
12340
|
+
}, [question.multiSelect]);
|
|
12341
|
+
const canSubmit = useMemo(() => selected.length > 0 && !submitting, [selected.length, submitting]);
|
|
12342
|
+
return (jsxs(VoteWrapper, { children: [category && jsx(VoteCategoryLabel, { children: category.name }), jsx(VoteQuestionText, { children: question.text }), jsx(VoteVariantList, { column: true, value: selected[0] ?? "", children: results.map(({ id, text, count, percent }) => (jsxs(VoteVariantRow, { children: [jsxs(VoteVariantHead, { children: [showControls &&
|
|
12343
|
+
(question.multiSelect ? (jsx(Checkbox, { checked: selected.includes(id), onChange: () => toggle(id) })) : (jsx(Radio, { value: id, checked: selected.includes(id), onChange: () => toggle(id) }))), jsx(VoteVariantText, { children: text }), jsx(VoteVariantCount, { children: count })] }), jsx(LinearProgress, { done: percent })] }, id))) }), screen === "unauthenticated" && (jsx(VoteAuthHint, { children: t("vote.authHint", { ns: VOTE_NS, defaultValue: "Чтобы проголосовать, пожалуйста, авторизуйтесь" }) })), screen === "voted" && (jsxs(VoteAcceptedBadge, { children: [jsx(Icon, { kind: "success" }), t("vote.accepted", { ns: VOTE_NS, defaultValue: "Ваш ответ принят" })] })), showControls && (jsx(RaisedButton, { primary: true, disabled: !canSubmit, onClick: () => onSubmit(selected), children: t("vote.submit", { ns: VOTE_NS, defaultValue: "Отправить ответ" }) })), canEdit && onEdit && (jsx(Flex, { children: jsx(VoteEditLink, { onClick: onEdit, children: t("vote.edit", { ns: VOTE_NS, defaultValue: "Редактировать" }) }) })), showShare && jsx(VoteShare, {})] }));
|
|
12344
|
+
};
|
|
12345
|
+
|
|
12346
|
+
/**
|
|
12347
|
+
* Тело контейнера: выбирает экран по вью-модели голосования.
|
|
12348
|
+
*
|
|
12349
|
+
* Живёт отдельно от `VoteContainer`, потому что тот отвечает за корень контейнера — размеры,
|
|
12350
|
+
* фон и заголовок — и не должен раздуваться ветвлением по состояниям.
|
|
12351
|
+
*/
|
|
12352
|
+
const VoteScreen = ({ screen, categories, question, category, results, totalVotes, isOwner, hasAnswers, isEditing, setIsEditing, editInitialValues, submitting, createVote, submitAnswer, editVote, deleteVote, }) => {
|
|
12353
|
+
const handleCreate = useCallback((values) => void createVote(values), [createVote]);
|
|
12354
|
+
const handleEdit = useCallback((values) => void editVote(values).then(() => setIsEditing(false)), [editVote, setIsEditing]);
|
|
12355
|
+
const handleDelete = useCallback(() => void deleteVote().then(() => setIsEditing(false)), [deleteVote, setIsEditing]);
|
|
12356
|
+
const handleCancel = useCallback(() => setIsEditing(false), [setIsEditing]);
|
|
12357
|
+
const handleEditOpen = useCallback(() => setIsEditing(true), [setIsEditing]);
|
|
12358
|
+
const handleSubmitAnswer = useCallback((variantIds) => void submitAnswer(variantIds), [submitAnswer]);
|
|
12359
|
+
if (screen === "loading")
|
|
12360
|
+
return jsx(ContainerLoading, {});
|
|
12361
|
+
if (isEditing && question) {
|
|
12362
|
+
return (jsx(VoteCreateForm, { categories: categories, initialValues: editInitialValues, isEditing: true, canDelete: !hasAnswers, submitting: submitting, onSubmit: handleEdit, onDelete: handleDelete, onCancel: handleCancel }));
|
|
12363
|
+
}
|
|
12364
|
+
if (screen === "create") {
|
|
12365
|
+
return (jsx(VoteCreateForm, { categories: categories, isEditing: false, canDelete: false, submitting: submitting, onSubmit: handleCreate }));
|
|
12366
|
+
}
|
|
12367
|
+
if (!question)
|
|
12368
|
+
return null;
|
|
12369
|
+
return (jsx(VoteResults, { question: question, category: category, results: results, totalVotes: totalVotes, screen: screen, isOwner: isOwner, hasAnswers: hasAnswers, submitting: submitting, onSubmit: handleSubmitAnswer, onEdit: handleEditOpen }));
|
|
12370
|
+
};
|
|
12371
|
+
|
|
12372
|
+
/** Приводит значение атрибута к строке-идентификатору (id приходят числами/строками). */
|
|
12373
|
+
const toId = (value) => (value === null || value === undefined ? "" : String(value));
|
|
12374
|
+
/**
|
|
12375
|
+
* Условие равенства по числовому полю для `conditions` слоя: `field == value`.
|
|
12376
|
+
*
|
|
12377
|
+
* Двойное равенство — форма, которой написаны условия во всех конфигах проекта; лексер языка
|
|
12378
|
+
* условий принимает и одиночное `=`, но расходиться с авторскими конфигами незачем.
|
|
12379
|
+
*/
|
|
12380
|
+
const eqCondition = (field, value) => `${field} == ${value}`;
|
|
12381
|
+
const props = (feature) => feature.properties ?? {};
|
|
12382
|
+
const mapCategories = (features) => features.map(feature => ({
|
|
12383
|
+
id: toId(props(feature)[VOTE_FIELDS.category.id] ?? feature.id),
|
|
12384
|
+
name: toId(props(feature)[VOTE_FIELDS.category.name]),
|
|
12385
|
+
}));
|
|
12386
|
+
const mapQuestion = (feature) => {
|
|
12387
|
+
const featureProps = props(feature);
|
|
12388
|
+
return {
|
|
12389
|
+
id: toId(featureProps[VOTE_FIELDS.question.id] ?? feature.id),
|
|
12390
|
+
text: toId(featureProps[VOTE_FIELDS.question.text]),
|
|
12391
|
+
categoryId: toId(featureProps[VOTE_FIELDS.question.categoryId]),
|
|
12392
|
+
userName: toId(featureProps[VOTE_FIELDS.question.userName]),
|
|
12393
|
+
multiSelect: Boolean(featureProps[VOTE_FIELDS.question.multiSelect]),
|
|
12394
|
+
};
|
|
12395
|
+
};
|
|
12396
|
+
const mapVariants = (features) => features.map(feature => ({
|
|
12397
|
+
id: toId(props(feature)[VOTE_FIELDS.variant.id] ?? feature.id),
|
|
12398
|
+
text: toId(props(feature)[VOTE_FIELDS.variant.text]),
|
|
12399
|
+
}));
|
|
12400
|
+
/** Список `variant_id` из ответов конкретного пользователя. */
|
|
12401
|
+
const getUserVariantIds = (answers, userName) => answers
|
|
12402
|
+
.filter(answer => toId(props(answer)[VOTE_FIELDS.answer.userName]) === userName)
|
|
12403
|
+
.map(answer => toId(props(answer)[VOTE_FIELDS.answer.variantId]));
|
|
12404
|
+
/** Id записей-ответов конкретного пользователя (для замены ответа при переголосовании). */
|
|
12405
|
+
const getUserAnswerIds = (answers, userName) => answers
|
|
12406
|
+
.filter(answer => toId(props(answer)[VOTE_FIELDS.answer.userName]) === userName)
|
|
12407
|
+
.map(answer => toId(answer.id))
|
|
12408
|
+
.filter(Boolean);
|
|
12409
|
+
/**
|
|
12410
|
+
* Считает голоса по вариантам. `percent` — доля голосов варианта от общего числа голосов
|
|
12411
|
+
* по голосованию.
|
|
12412
|
+
*/
|
|
12413
|
+
const computeResults = (variants, answers) => {
|
|
12414
|
+
const counts = new Map();
|
|
12415
|
+
answers.forEach(answer => {
|
|
12416
|
+
const variantId = toId(props(answer)[VOTE_FIELDS.answer.variantId]);
|
|
12417
|
+
counts.set(variantId, (counts.get(variantId) ?? 0) + 1);
|
|
12418
|
+
});
|
|
12419
|
+
const totalVotes = answers.length;
|
|
12420
|
+
const results = variants.map(variant => {
|
|
12421
|
+
const count = counts.get(variant.id) ?? 0;
|
|
12422
|
+
return {
|
|
12423
|
+
...variant,
|
|
12424
|
+
count,
|
|
12425
|
+
percent: totalVotes ? Math.round((count / totalVotes) * PERCENT_BASE) : 0,
|
|
12426
|
+
};
|
|
12427
|
+
});
|
|
12428
|
+
return { results, totalVotes };
|
|
12429
|
+
};
|
|
12430
|
+
/**
|
|
12431
|
+
* Сравнивает варианты из формы с существующими в БД и раскладывает на наборы операций:
|
|
12432
|
+
* создать (новые без id), обновить (изменился текст) и удалить (исчезли из формы).
|
|
12433
|
+
*/
|
|
12434
|
+
const diffVariants = (existing, next) => {
|
|
12435
|
+
const nextIds = new Set(next.filter(({ id }) => id).map(({ id }) => id));
|
|
12436
|
+
const existingById = new Map(existing.map(variant => [variant.id, variant]));
|
|
12437
|
+
const toCreate = next.filter(({ id, text }) => !id && text.trim()).map(({ text }) => text.trim());
|
|
12438
|
+
const toUpdate = next
|
|
12439
|
+
.filter(({ id, text }) => id && existingById.get(id) && existingById.get(id)?.text !== text.trim())
|
|
12440
|
+
.map(({ id, text }) => ({ id: id, text: text.trim() }));
|
|
12441
|
+
const toDeleteIds = existing.filter(({ id }) => !nextIds.has(id)).map(({ id }) => id);
|
|
12442
|
+
return { toCreate, toUpdate, toDeleteIds };
|
|
12443
|
+
};
|
|
12444
|
+
|
|
12445
|
+
/**
|
|
12446
|
+
* Операции записи контейнера: создание голосования, отправка ответа, редактирование и удаление.
|
|
12447
|
+
* Все операции доступны только авторизованному пользователю.
|
|
12448
|
+
*/
|
|
12449
|
+
const useVoteActions = ({ dataSources, attributeName, featureId, featureLayerName, username, question, variants, answers, onQuestionIdChange, reload, }) => {
|
|
12450
|
+
const { api } = useGlobalContext();
|
|
12451
|
+
const { questionDataSource, variantDataSource, answerDataSource } = dataSources;
|
|
12452
|
+
const [submitting, setSubmitting] = useState(false);
|
|
12453
|
+
const writeFeatureAttribute = useCallback(async (value) => {
|
|
12454
|
+
if (!api || !featureLayerName || !featureId || !attributeName)
|
|
12455
|
+
return;
|
|
12456
|
+
await api.layers.updateFeature(featureLayerName, [{ id: featureId, attributes: { [attributeName]: value } }]);
|
|
12457
|
+
}, [api, featureLayerName, featureId, attributeName]);
|
|
12458
|
+
const createVote = useCallback(async (values) => {
|
|
12459
|
+
if (!api)
|
|
12460
|
+
return;
|
|
12461
|
+
setSubmitting(true);
|
|
12462
|
+
try {
|
|
12463
|
+
const questionResult = await api.layers.createFeatures({ name: questionDataSource }, [
|
|
12464
|
+
{
|
|
12465
|
+
properties: {
|
|
12466
|
+
[VOTE_FIELDS.question.text]: values.text,
|
|
12467
|
+
[VOTE_FIELDS.question.categoryId]: values.categoryId,
|
|
12468
|
+
[VOTE_FIELDS.question.userName]: username,
|
|
12469
|
+
[VOTE_FIELDS.question.multiSelect]: values.multiSelect,
|
|
12470
|
+
},
|
|
12471
|
+
},
|
|
12472
|
+
]);
|
|
12473
|
+
const newQuestionId = questionResult.createdIds?.[0];
|
|
12474
|
+
if (!newQuestionId)
|
|
12475
|
+
return;
|
|
12476
|
+
await api.layers.createFeatures({ name: variantDataSource }, values.variants
|
|
12477
|
+
.filter(({ text }) => text.trim())
|
|
12478
|
+
.map(({ text }) => ({
|
|
12479
|
+
properties: {
|
|
12480
|
+
[VOTE_FIELDS.variant.questionId]: newQuestionId,
|
|
12481
|
+
[VOTE_FIELDS.variant.text]: text.trim(),
|
|
12482
|
+
},
|
|
12483
|
+
})));
|
|
12484
|
+
await writeFeatureAttribute(newQuestionId);
|
|
12485
|
+
onQuestionIdChange(newQuestionId);
|
|
12486
|
+
reload();
|
|
12487
|
+
}
|
|
12488
|
+
finally {
|
|
12489
|
+
setSubmitting(false);
|
|
12490
|
+
}
|
|
12491
|
+
}, [api, questionDataSource, variantDataSource, username, writeFeatureAttribute, onQuestionIdChange, reload]);
|
|
12492
|
+
const submitAnswer = useCallback(async (variantIds) => {
|
|
12493
|
+
if (!api || !question || !variantIds.length)
|
|
12494
|
+
return;
|
|
12495
|
+
setSubmitting(true);
|
|
12496
|
+
try {
|
|
12497
|
+
// Заложено на будущее (переголосование без UI): сначала снимаем прежние ответы пользователя.
|
|
12498
|
+
const previousAnswerIds = getUserAnswerIds(answers, username);
|
|
12499
|
+
if (previousAnswerIds.length) {
|
|
12500
|
+
await api.layers.deleteFeatures({ name: answerDataSource, ids: previousAnswerIds });
|
|
12501
|
+
}
|
|
12502
|
+
await api.layers.createFeatures({ name: answerDataSource }, variantIds.map(variantId => ({
|
|
12503
|
+
properties: {
|
|
12504
|
+
[VOTE_FIELDS.answer.questionId]: question.id,
|
|
12505
|
+
[VOTE_FIELDS.answer.userName]: username,
|
|
12506
|
+
[VOTE_FIELDS.answer.variantId]: variantId,
|
|
12507
|
+
},
|
|
12508
|
+
})));
|
|
12509
|
+
reload();
|
|
12510
|
+
}
|
|
12511
|
+
finally {
|
|
12512
|
+
setSubmitting(false);
|
|
12513
|
+
}
|
|
12514
|
+
}, [api, question, answers, username, answerDataSource, reload]);
|
|
12515
|
+
const editVote = useCallback(async (values) => {
|
|
12516
|
+
if (!api || !question)
|
|
12517
|
+
return;
|
|
12518
|
+
setSubmitting(true);
|
|
12519
|
+
try {
|
|
12520
|
+
await api.layers.updateFeature(questionDataSource, [
|
|
12521
|
+
{
|
|
12522
|
+
id: question.id,
|
|
12523
|
+
attributes: {
|
|
12524
|
+
[VOTE_FIELDS.question.text]: values.text,
|
|
12525
|
+
[VOTE_FIELDS.question.categoryId]: values.categoryId,
|
|
12526
|
+
[VOTE_FIELDS.question.multiSelect]: values.multiSelect,
|
|
12527
|
+
},
|
|
12528
|
+
},
|
|
12529
|
+
]);
|
|
12530
|
+
const { toCreate, toUpdate, toDeleteIds } = diffVariants(variants, values.variants);
|
|
12531
|
+
if (toCreate.length) {
|
|
12532
|
+
await api.layers.createFeatures({ name: variantDataSource }, toCreate.map(text => ({
|
|
12533
|
+
properties: { [VOTE_FIELDS.variant.questionId]: question.id, [VOTE_FIELDS.variant.text]: text },
|
|
12534
|
+
})));
|
|
12535
|
+
}
|
|
12536
|
+
if (toUpdate.length) {
|
|
12537
|
+
await api.layers.updateFeature(variantDataSource, toUpdate.map(({ id, text }) => ({ id, attributes: { [VOTE_FIELDS.variant.text]: text } })));
|
|
12538
|
+
}
|
|
12539
|
+
if (toDeleteIds.length) {
|
|
12540
|
+
await api.layers.deleteFeatures({ name: variantDataSource, ids: toDeleteIds });
|
|
12541
|
+
}
|
|
12542
|
+
reload();
|
|
12543
|
+
}
|
|
12544
|
+
finally {
|
|
12545
|
+
setSubmitting(false);
|
|
12546
|
+
}
|
|
12547
|
+
}, [api, question, questionDataSource, variantDataSource, variants, reload]);
|
|
12548
|
+
const deleteVote = useCallback(async () => {
|
|
12549
|
+
if (!api || !question)
|
|
12550
|
+
return;
|
|
12551
|
+
setSubmitting(true);
|
|
12552
|
+
try {
|
|
12553
|
+
const variantIds = variants.map(({ id }) => id).filter(Boolean);
|
|
12554
|
+
if (variantIds.length) {
|
|
12555
|
+
await api.layers.deleteFeatures({ name: variantDataSource, ids: variantIds });
|
|
12556
|
+
}
|
|
12557
|
+
await api.layers.deleteFeature({ name: questionDataSource, id: question.id });
|
|
12558
|
+
await writeFeatureAttribute(null);
|
|
12559
|
+
onQuestionIdChange("");
|
|
12560
|
+
reload();
|
|
12561
|
+
}
|
|
12562
|
+
finally {
|
|
12563
|
+
setSubmitting(false);
|
|
12564
|
+
}
|
|
12565
|
+
}, [api, question, variants, variantDataSource, questionDataSource, writeFeatureAttribute, onQuestionIdChange, reload]);
|
|
12566
|
+
return { submitting, createVote, submitAnswer, editVote, deleteVote };
|
|
12567
|
+
};
|
|
12568
|
+
|
|
12569
|
+
/**
|
|
12570
|
+
* Загружает справочник категорий, а по `questionId` — само голосование, его варианты и ответы.
|
|
12571
|
+
* Перечитывается при смене `questionId` или `reloadToken` (после создания/голосования/правки).
|
|
12572
|
+
*/
|
|
12573
|
+
const useVoteData = ({ dataSources, questionId, reloadToken }) => {
|
|
12574
|
+
const { api } = useGlobalContext();
|
|
12575
|
+
const { categoryDataSource, questionDataSource, variantDataSource, answerDataSource } = dataSources;
|
|
12576
|
+
const [categories, setCategories] = useState([]);
|
|
12577
|
+
const [question, setQuestion] = useState(null);
|
|
12578
|
+
const [variants, setVariants] = useState([]);
|
|
12579
|
+
const [answers, setAnswers] = useState([]);
|
|
12580
|
+
const [loading, setLoading] = useState(false);
|
|
12581
|
+
const [error, setError] = useState(false);
|
|
12582
|
+
useEffect(() => {
|
|
12583
|
+
let active = true;
|
|
12584
|
+
if (api && categoryDataSource) {
|
|
12585
|
+
api.layers
|
|
12586
|
+
.getFeatures(categoryDataSource, { withGeom: false, limit: VOTE_FETCH_LIMIT })
|
|
12587
|
+
.then(response => {
|
|
12588
|
+
if (active)
|
|
12589
|
+
setCategories(mapCategories(response.features ?? []));
|
|
12590
|
+
})
|
|
12591
|
+
.catch(() => undefined);
|
|
12592
|
+
}
|
|
12593
|
+
return () => {
|
|
12594
|
+
active = false;
|
|
12595
|
+
};
|
|
12596
|
+
}, [api, categoryDataSource]);
|
|
12597
|
+
useEffect(() => {
|
|
12598
|
+
let active = true;
|
|
12599
|
+
if (!api || !questionId) {
|
|
12600
|
+
setQuestion(null);
|
|
12601
|
+
setVariants([]);
|
|
12602
|
+
setAnswers([]);
|
|
12603
|
+
return () => {
|
|
12604
|
+
active = false;
|
|
12605
|
+
};
|
|
12606
|
+
}
|
|
12607
|
+
setLoading(true);
|
|
12608
|
+
setError(false);
|
|
12609
|
+
Promise.all([
|
|
12610
|
+
api.layers.getFeatures(questionDataSource, {
|
|
12611
|
+
conditions: [eqCondition(VOTE_FIELDS.question.id, questionId)],
|
|
12612
|
+
withGeom: false,
|
|
12613
|
+
limit: 1,
|
|
12614
|
+
}),
|
|
12615
|
+
api.layers.getFeatures(variantDataSource, {
|
|
12616
|
+
conditions: [eqCondition(VOTE_FIELDS.variant.questionId, questionId)],
|
|
12617
|
+
withGeom: false,
|
|
12618
|
+
limit: VOTE_FETCH_LIMIT,
|
|
12619
|
+
}),
|
|
12620
|
+
api.layers.getFeatures(answerDataSource, {
|
|
12621
|
+
conditions: [eqCondition(VOTE_FIELDS.answer.questionId, questionId)],
|
|
12622
|
+
withGeom: false,
|
|
12623
|
+
limit: VOTE_FETCH_LIMIT,
|
|
12624
|
+
}),
|
|
12625
|
+
])
|
|
12626
|
+
.then(([questionResponse, variantResponse, answerResponse]) => {
|
|
12627
|
+
if (!active)
|
|
12628
|
+
return;
|
|
12629
|
+
const questionFeature = questionResponse.features?.[0];
|
|
12630
|
+
setQuestion(questionFeature ? mapQuestion(questionFeature) : null);
|
|
12631
|
+
setVariants(mapVariants(variantResponse.features ?? []));
|
|
12632
|
+
setAnswers(answerResponse.features ?? []);
|
|
12633
|
+
})
|
|
12634
|
+
.catch(() => {
|
|
12635
|
+
if (active)
|
|
12636
|
+
setError(true);
|
|
12637
|
+
})
|
|
12638
|
+
.finally(() => {
|
|
12639
|
+
if (active)
|
|
12640
|
+
setLoading(false);
|
|
12641
|
+
});
|
|
12642
|
+
return () => {
|
|
12643
|
+
active = false;
|
|
12644
|
+
};
|
|
12645
|
+
}, [api, questionId, reloadToken, questionDataSource, variantDataSource, answerDataSource]);
|
|
12646
|
+
const { results, totalVotes } = useMemo(() => computeResults(variants, answers), [variants, answers]);
|
|
12647
|
+
return { categories, question, variants, answers, results, totalVotes, loading, error };
|
|
12648
|
+
};
|
|
12649
|
+
|
|
12650
|
+
/**
|
|
12651
|
+
* Текущий пользователь для контейнера голосования. Авторизация обязательна — неавторизованный
|
|
12652
|
+
* пользователь видит только результаты. Источник — `api.account.getUserInfo()`; ошибка/отсутствие
|
|
12653
|
+
* `username` трактуются как «не авторизован».
|
|
12654
|
+
*
|
|
12655
|
+
* Публичный аккаунт портала (`PUBLIC_USER_NAME` в `username` или в ролях) авторизацией не
|
|
12656
|
+
* считается: под ним ходят ВСЕ анонимные посетители шаренного проекта, поэтому и «мой голос»,
|
|
12657
|
+
* и владение голосованием опознались бы у них общими на всех.
|
|
12658
|
+
*/
|
|
12659
|
+
const useVoteUser = () => {
|
|
12660
|
+
const { api } = useGlobalContext();
|
|
12661
|
+
const [username, setUsername] = useState("");
|
|
12662
|
+
const [roles, setRoles] = useState([]);
|
|
12663
|
+
const [loading, setLoading] = useState(true);
|
|
12664
|
+
useEffect(() => {
|
|
12665
|
+
let active = true;
|
|
12666
|
+
if (!api) {
|
|
12667
|
+
setLoading(false);
|
|
12668
|
+
return () => {
|
|
12669
|
+
active = false;
|
|
12670
|
+
};
|
|
12671
|
+
}
|
|
12672
|
+
api.account
|
|
12673
|
+
.getUserInfo()
|
|
12674
|
+
.then(info => {
|
|
12675
|
+
if (!active)
|
|
12676
|
+
return;
|
|
12677
|
+
setUsername(info?.username ?? "");
|
|
12678
|
+
setRoles(info?.roles ?? []);
|
|
12679
|
+
})
|
|
12680
|
+
.catch(() => {
|
|
12681
|
+
if (!active)
|
|
12682
|
+
return;
|
|
12683
|
+
setUsername("");
|
|
12684
|
+
setRoles([]);
|
|
12685
|
+
})
|
|
12686
|
+
.finally(() => {
|
|
12687
|
+
if (active)
|
|
12688
|
+
setLoading(false);
|
|
12689
|
+
});
|
|
12690
|
+
return () => {
|
|
12691
|
+
active = false;
|
|
12692
|
+
};
|
|
12693
|
+
}, [api]);
|
|
12694
|
+
const isPublic = useMemo(() => username === PUBLIC_USER_NAME || roles.includes(PUBLIC_USER_NAME), [username, roles]);
|
|
12695
|
+
const isAuthenticated = !!username && !isPublic;
|
|
12696
|
+
return { username, isAuthenticated, loading };
|
|
12697
|
+
};
|
|
12698
|
+
|
|
12699
|
+
const normalizeAttributeName = (attributeName) => {
|
|
12700
|
+
if (Array.isArray(attributeName))
|
|
12701
|
+
return attributeName[0] ?? "";
|
|
12702
|
+
return attributeName ?? "";
|
|
12703
|
+
};
|
|
12704
|
+
/**
|
|
12705
|
+
* Вью-модель контейнера голосования: определяет текущий экран и собирает данные/действия.
|
|
12706
|
+
* Привязка к объекту карты — через `elementConfig.attributeName` (хранит `question_id`).
|
|
12707
|
+
*/
|
|
12708
|
+
const useVoteContainer = (elementConfig) => {
|
|
12709
|
+
const { feature, layerInfo } = useWidgetContext(WidgetType.FeatureCard);
|
|
12710
|
+
const options = elementConfig?.options;
|
|
12711
|
+
const attributeName = normalizeAttributeName(elementConfig?.attributeName);
|
|
12712
|
+
const dataSources = useMemo(() => ({
|
|
12713
|
+
categoryDataSource: options?.categoryDataSource ?? "",
|
|
12714
|
+
questionDataSource: options?.questionDataSource ?? "",
|
|
12715
|
+
variantDataSource: options?.variantDataSource ?? "",
|
|
12716
|
+
answerDataSource: options?.answerDataSource ?? "",
|
|
12717
|
+
}), [options]);
|
|
12718
|
+
const configured = useMemo(() => Boolean(attributeName &&
|
|
12719
|
+
dataSources.categoryDataSource &&
|
|
12720
|
+
dataSources.questionDataSource &&
|
|
12721
|
+
dataSources.variantDataSource &&
|
|
12722
|
+
dataSources.answerDataSource), [attributeName, dataSources]);
|
|
12723
|
+
const { username, isAuthenticated, loading: userLoading } = useVoteUser();
|
|
12724
|
+
const featureId = feature?.id;
|
|
12725
|
+
const featureQuestionId = toId(feature?.properties?.[attributeName]);
|
|
12726
|
+
const [overrideQuestionId, setOverrideQuestionId] = useState(null);
|
|
12727
|
+
useEffect(() => {
|
|
12728
|
+
setOverrideQuestionId(null);
|
|
12729
|
+
}, [featureId]);
|
|
12730
|
+
const questionId = overrideQuestionId ?? featureQuestionId;
|
|
12731
|
+
const [reloadToken, setReloadToken] = useState(0);
|
|
12732
|
+
const reload = useCallback(() => setReloadToken(token => token + 1), []);
|
|
12733
|
+
const onQuestionIdChange = useCallback((id) => setOverrideQuestionId(id), []);
|
|
12734
|
+
const { categories, question, variants, answers, results, totalVotes, loading } = useVoteData({
|
|
12735
|
+
dataSources,
|
|
12736
|
+
questionId,
|
|
12737
|
+
reloadToken,
|
|
12738
|
+
});
|
|
12739
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
12740
|
+
const actions = useVoteActions({
|
|
12741
|
+
dataSources,
|
|
12742
|
+
attributeName,
|
|
12743
|
+
featureId,
|
|
12744
|
+
featureLayerName: layerInfo?.name,
|
|
12745
|
+
username,
|
|
12746
|
+
question,
|
|
12747
|
+
variants,
|
|
12748
|
+
answers,
|
|
12749
|
+
onQuestionIdChange,
|
|
12750
|
+
reload,
|
|
12751
|
+
});
|
|
12752
|
+
const hasAnswers = answers.length > 0;
|
|
12753
|
+
const isOwner = isAuthenticated && !!question && question.userName === username;
|
|
12754
|
+
const hasVoted = useMemo(() => getUserVariantIds(answers, username).length > 0, [answers, username]);
|
|
12755
|
+
const category = useMemo(() => categories.find(({ id }) => id === question?.categoryId), [categories, question?.categoryId]);
|
|
12756
|
+
const screen = useMemo(() => {
|
|
12757
|
+
if (!configured)
|
|
12758
|
+
return null;
|
|
12759
|
+
if (userLoading)
|
|
12760
|
+
return "loading";
|
|
12761
|
+
if (!questionId)
|
|
12762
|
+
return isAuthenticated ? "create" : null;
|
|
12763
|
+
if (loading && !question)
|
|
12764
|
+
return "loading";
|
|
12765
|
+
if (!question)
|
|
12766
|
+
return null;
|
|
12767
|
+
if (!isAuthenticated)
|
|
12768
|
+
return "unauthenticated";
|
|
12769
|
+
return hasVoted ? "voted" : "voting";
|
|
12770
|
+
}, [configured, userLoading, questionId, isAuthenticated, loading, question, hasVoted]);
|
|
12771
|
+
const editInitialValues = useMemo(() => question
|
|
12772
|
+
? {
|
|
12773
|
+
categoryId: question.categoryId,
|
|
12774
|
+
text: question.text,
|
|
12775
|
+
multiSelect: question.multiSelect,
|
|
12776
|
+
variants: variants.map(({ id, text }) => ({ id, text })),
|
|
12777
|
+
}
|
|
12778
|
+
: undefined, [question, variants]);
|
|
12779
|
+
return {
|
|
12780
|
+
configured,
|
|
12781
|
+
screen,
|
|
12782
|
+
categories,
|
|
12783
|
+
question,
|
|
12784
|
+
category,
|
|
12785
|
+
results,
|
|
12786
|
+
totalVotes,
|
|
12787
|
+
isOwner,
|
|
12788
|
+
hasAnswers,
|
|
12789
|
+
isEditing,
|
|
12790
|
+
setIsEditing,
|
|
12791
|
+
editInitialValues,
|
|
12792
|
+
...actions,
|
|
12793
|
+
};
|
|
12794
|
+
};
|
|
12795
|
+
|
|
12796
|
+
/**
|
|
12797
|
+
* Контейнер «Голосование»: привязывает опрос к объекту карты через `attributeName`
|
|
12798
|
+
* и показывает один из экранов — создание, голосование, после голосования
|
|
12799
|
+
* или (для неавторизованных) только результаты.
|
|
12800
|
+
*
|
|
12801
|
+
* Корнем владеет сам (`useContainerRoot`), поэтому шаблон записан в `ROOT_OWNING_TEMPLATES`:
|
|
12802
|
+
* размеры, авторский `style`, фон и заголовок живут на одном узле, без внешней обёртки.
|
|
12803
|
+
*/
|
|
12804
|
+
const VoteContainer = memo(({ type, elementConfig, isVisible, renderElement }) => {
|
|
12805
|
+
const { root, body } = useContainerRoot({ elementConfig });
|
|
12806
|
+
const { configured, ...vote } = useVoteContainer(elementConfig);
|
|
12807
|
+
// Без имени атрибута или хотя бы одного источника контейнеру не с чем работать: раньше он
|
|
12808
|
+
// молча возвращал null, и опечатка в конфиге выглядела как «блока просто нет».
|
|
12809
|
+
if (!configured) {
|
|
12810
|
+
return jsx(DataSourceError, { name: elementConfig?.templateName ?? ContainerTemplate.Vote });
|
|
12811
|
+
}
|
|
12812
|
+
// Голосования нет, а создавать его посетитель не вправе — показывать нечего. Пустой заголовок
|
|
12813
|
+
// в карточке в этом случае тоже не нужен, поэтому не рисуем и корень.
|
|
12814
|
+
if (!vote.screen)
|
|
12815
|
+
return null;
|
|
12816
|
+
return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsx(Container, { ...body, isColumn: true, children: jsx(VoteScreen, { ...vote, screen: vote.screen }) }))] }));
|
|
12817
|
+
});
|
|
12818
|
+
|
|
12026
12819
|
const ExpandableTitle = memo(({ elementConfig, type, renderElement }) => {
|
|
12027
12820
|
const { expandedContainers } = useWidgetContext(type);
|
|
12028
12821
|
const { id, options, templateName, children } = elementConfig || {};
|
|
@@ -13247,6 +14040,8 @@ const EditAttachmentContainer = memo(({ type, elementConfig, renderElement }) =>
|
|
|
13247
14040
|
setPreviewIndex(idx);
|
|
13248
14041
|
}, [items]);
|
|
13249
14042
|
const handleClosePreview = useCallback(() => setPreviewIndex(null), []);
|
|
14043
|
+
const downloadByIndex = useAttachmentDownload(items);
|
|
14044
|
+
const handleDownload = useCallback((_image, index) => downloadByIndex(index), [downloadByIndex]);
|
|
13250
14045
|
const handleShowMore = useCallback(() => setShowMore(true), [setShowMore]);
|
|
13251
14046
|
const handleDelete = useCallback((link) => {
|
|
13252
14047
|
persist(items.filter(item => item.link !== link));
|
|
@@ -13297,7 +14092,7 @@ const EditAttachmentContainer = memo(({ type, elementConfig, renderElement }) =>
|
|
|
13297
14092
|
setUploading(false);
|
|
13298
14093
|
}
|
|
13299
14094
|
}, [api, items, parentResourceId, persist]);
|
|
13300
|
-
return (jsxs(AttachmentsContainer, { id: id, style: { ...BASE_CONTAINER_STYLE, ...style }, ...bgHost, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(AttachmentsHeader, { alias: renderElement?.({ id: "alias" }), count: items.length, viewMode: viewMode, onChangeViewMode: setViewMode }), jsx(AttachmentsContent, { children: viewMode === "grid" ? (jsx(AttachmentsGrid, { items: visibleItems, isEdit: true, onPreview: handlePreview, onDelete: handleDelete })) : (jsx(AttachmentsList, { items: visibleItems, isEdit: true, onPreview: handlePreview, onDelete: handleDelete })) }), hasMore && !showMore && (jsx(ShowMoreButton, { hiddenCount: hiddenCount, onClick: handleShowMore })), jsx(AddButton, { accept: fileExtensions, onSelectFiles: uploading ? () => undefined : handleUpload, onSelectFromCatalog: handleSelectFromCatalog, onSelectFromLink: handleOpenLinkDialog }), jsx(AttachmentLinkDialog, { isOpen: isLinkDialogOpen, onClose: handleCloseLinkDialog, onSubmit: handleAddByLink }), previewIndex !== null && (jsx(Preview, { images: previewImages, initialIndex: previewIndex, isOpen: previewIndex !== null, onClose: handleClosePreview }, previewIndex))] }));
|
|
14095
|
+
return (jsxs(AttachmentsContainer, { id: id, style: { ...BASE_CONTAINER_STYLE, ...style }, ...bgHost, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(AttachmentsHeader, { alias: renderElement?.({ id: "alias" }), count: items.length, viewMode: viewMode, onChangeViewMode: setViewMode }), jsx(AttachmentsContent, { children: viewMode === "grid" ? (jsx(AttachmentsGrid, { items: visibleItems, isEdit: true, onPreview: handlePreview, onDelete: handleDelete })) : (jsx(AttachmentsList, { items: visibleItems, isEdit: true, onPreview: handlePreview, onDelete: handleDelete })) }), hasMore && !showMore && (jsx(ShowMoreButton, { hiddenCount: hiddenCount, onClick: handleShowMore })), jsx(AddButton, { accept: fileExtensions, onSelectFiles: uploading ? () => undefined : handleUpload, onSelectFromCatalog: handleSelectFromCatalog, onSelectFromLink: handleOpenLinkDialog }), jsx(AttachmentLinkDialog, { isOpen: isLinkDialogOpen, onClose: handleCloseLinkDialog, onSubmit: handleAddByLink }), previewIndex !== null && (jsx(Preview, { images: previewImages, initialIndex: previewIndex, isOpen: previewIndex !== null, onClose: handleClosePreview, onDownload: handleDownload }, previewIndex))] }));
|
|
13301
14096
|
});
|
|
13302
14097
|
|
|
13303
14098
|
// `ProgressContainer` и `RoundedBackgroundContainer` исторически принимают `InnerContainerProps`
|
|
@@ -13351,6 +14146,7 @@ const createContainerComponents = () => ({
|
|
|
13351
14146
|
[ContainerTemplate.EditAttachment]: EditAttachmentContainer,
|
|
13352
14147
|
[ContainerTemplate.Attachment]: AttachmentContainer,
|
|
13353
14148
|
[ContainerTemplate.EditGroup]: EditGroupContainer,
|
|
14149
|
+
[ContainerTemplate.Vote]: VoteContainer,
|
|
13354
14150
|
[ContainerTemplate.ContainersGroup]: ContainersGroupContainer,
|
|
13355
14151
|
[ContainerTemplate.GridRow]: GridRowContainer,
|
|
13356
14152
|
[ContainerTemplate.StructuredData]: StructuredDataContainer,
|
|
@@ -13386,6 +14182,7 @@ const ROOT_OWNING_TEMPLATES = new Set([
|
|
|
13386
14182
|
ContainerTemplate.StructuredData,
|
|
13387
14183
|
ContainerTemplate.Task,
|
|
13388
14184
|
ContainerTemplate.Upload,
|
|
14185
|
+
ContainerTemplate.Vote,
|
|
13389
14186
|
]);
|
|
13390
14187
|
let cachedContainerComponents = null;
|
|
13391
14188
|
const getContainerComponents = () => {
|
|
@@ -18454,5 +19251,5 @@ const DEFAULT_HEATMAP_STYLE = {
|
|
|
18454
19251
|
],
|
|
18455
19252
|
};
|
|
18456
19253
|
|
|
18457
|
-
export { ALIGNMENTS, ALIGN_ITEMS, ATTRIBUTE_ICON_ELEMENT_TYPES, AddButtonRow, AddFeatureButton, AddFeatureContainer, AlertIconContainer, AttachmentContainer, AttributeGalleryContainer, AttributeLabel, BASE_CONTAINER_STYLE, BG_IMAGE_SLOT_ID, BaseMapTheme, CHART_TYPES, COMPACT_FRACTION_DIGITS, CONFIG_PAGES_ID, CONFIG_PAGE_ID, CONTAINERS_GROUP_DEFAULTS, CONTAINER_BODY_ATTRIBUTE, CONTAINER_BODY_FILL_STYLE, CameraContainer, Chart, ChartContainer, ChartLegend, ChartLoading, Container, ContainerBackground, ContainerChildren, ContainerLoading, ContainerRoot, ContainerTemplate, ContainerWrapper, ContainersGroupContainer, DASHBOARD_OVERLAY_Z_INDEX, DEFAULT_ATTRIBUTE_NAME, DEFAULT_BARCHART_RADIUS, DEFAULT_BASE_MAP, DEFAULT_BLUR, DEFAULT_CHART_ANGLE, DEFAULT_CHART_HEIGHT, DEFAULT_CHART_WIDTH, DEFAULT_CIRCLE_RADIUS, DEFAULT_CIRCLE_STROKE_WIDTH, DEFAULT_CIRCLE_STYLE, DEFAULT_COLOR, DEFAULT_DASHBOARD_CONFIG, DEFAULT_DATA_SOURCE_LIMIT, DEFAULT_DROPDOWN_WIDTH, DEFAULT_FILL_EXTRUSION_BASE, DEFAULT_FILL_EXTRUSION_HEIGHT, DEFAULT_FILL_EXTRUSION_STYLE, DEFAULT_FILL_EXTRUSION_VERTICAL_GRADIENT, DEFAULT_FILL_STYLE, DEFAULT_FILTER_PADDING, DEFAULT_GRID_GAP, DEFAULT_HEATMAP_COLOR, DEFAULT_HEATMAP_INTENSITY, DEFAULT_HEATMAP_RADIUS, DEFAULT_HEATMAP_STYLE, DEFAULT_HEATMAP_WEIGHT, DEFAULT_ICON_ANCHOR, DEFAULT_ICON_OVERLAP, DEFAULT_ICON_PADDING, DEFAULT_ICON_ROTATE, DEFAULT_ICON_SIZE, DEFAULT_ID_ATTRIBUTE_NAME, DEFAULT_LAT, DEFAULT_LINE_CAP, DEFAULT_LINE_JOIN, DEFAULT_LINE_STYLE, DEFAULT_LINE_WIDTH, DEFAULT_LNG, DEFAULT_OPACITY, DEFAULT_PAGES_CONFIG, DEFAULT_PIECHART_RADIUS, DEFAULT_SYMBOL_COLOR, DEFAULT_SYMBOL_HALO_COLOR, DEFAULT_SYMBOL_PLACEMENT, DEFAULT_SYMBOL_SPACING, DEFAULT_SYMBOL_STYLE, DEFAULT_TEXT_ANCHOR, DEFAULT_TEXT_JUSTIFY, DEFAULT_TEXT_SIZE, DEFAULT_TEXT_TRANSFORM, DEFAULT_TRACK_FR, DEFAULT_TRANSLATE, DEFAULT_ZOOM, DRAG_THRESHOLD_PX, Dashboard, DashboardCheckbox, DashboardChip, DashboardContent, DashboardContext, DashboardDefaultHeader, DashboardHeader, DashboardLoading, DashboardPlaceholder, DashboardPlaceholderWrap, DashboardProvider, DashboardWrapper, DataSourceContainer, DataSourceError, DataSourceErrorContainer, DataSourceInnerContainer, DataSourceProgressContainer, DateFormat, DefaultAttributesContainer, DefaultHeaderContainer, DefaultHeaderWrapper, DividerContainer, EMPTY_DATA_SOURCE_LAYER_INFO, EXTENT_FILTER_NAME, EditGeometryType, ElementButton, ElementCamera, ElementChart, ElementChips, ElementControl, ElementIcon, ElementImage, ElementLegend, ElementLink, ElementMarkdown, ElementSlideshow, ElementSvg, ElementTable, ElementTooltip, ElementValueWrapper, ExpandableTitle, FEATURE_CARD_DEFAULT_COLORS, FEATURE_CARD_OTHER_COLOR, FILL_SIZE, FILTERED_VALUE_OPACITY, FILTER_PREFIX, FR_PRECISION, FeatureCardBackgroundHeader, FeatureCardButtons, FeatureCardContext, FeatureCardDefaultHeader, FeatureCardHeader, FeatureCardProvider, FeatureCardSlideshowHeader, FeatureCardTitle, FeatureControls, FeatureTitleContainer, FiltersContainer, GEOMETRY_ATTRIBUTE, GEOMETRY_FILTER_NAME, GRID_AUTO_FILL_DEFAULTS, GRID_CELL_ATTR, GRID_CELL_ID_PREFIX, GRID_DRAGGING_ATTR, GRID_DRAG_SOURCE_ATTR, GRID_DROP_TARGET_ATTR, GRID_FILL_DEFAULTS, GRID_HANDLE_ATTR, GRID_HANDLE_PROPS, GRID_ROW_ID_PREFIX, GlobalContext, GlobalProvider, GridRowContainer, HANDLE_BLEED_PX, Header, HeaderContainer, HeaderFontColorMixin, HeaderFrontView, HeaderTemplate, HeaderTitleContainer, HiddenTitleItems, IconContainer, ImageContainer, LEFT_PANEL_HEADER_HEIGHT, Layer, LayerDescription, LayerGroupList, LayerIcon, LayerIconContainer, LayerListContainer, LayerTree, LayersContainer, LayersListWrapper, LinearProgressContainer, LogTerminal, LogoContainer, MAP_VIEW_FILTER_NAMES, MAX_CHART_WIDTH, MAX_TRACKS, MIN_TRACK_PX, MIN_TRACK_RATIO, Map$1 as Map, MapContext, MapProvider, NON_TRACK_SLOT_IDS, NO_CELL_DRAG_SELECTOR, NO_CONTENT_VALUE, NUMERIC_ATTRIBUTE_TYPES, NoLiveSnapshotContainer, OBJECT_FITS, OVERFLOWS, OneColumnContainer, PIE_CHART_TOOLTIP_STYLE, POLL_SUBTASK_INTERVAL_MS, POLL_SUBTASK_TIMEOUT_MS, PROVIDER_PREFIX, PageNavigator, PageTitle, PageTitleContainer, PagesContainer, Pagination, PresentationHeader, PresentationHeaderButtons, PresentationHeaderTools, PresentationPanelContainer, PresentationPanelWrapper, PresentationWrapper, ProgressContainer, ProviderPrefix, QUERY_DESCRIPTION_CACHE_TTL, RoundedBackgroundContainer, SAVE_HOOK_RESULT_DURATION, SERVER_NOTIFICATION_EVENT, STACK_BAR_TOTAL_HEIGHT, ScalingFactor, ServerNotificationsContext, ServerNotificationsProvider, SlideshowContainer, SmallPreviewContainer$1 as SmallPreviewContainer, SmallPreviewControl, SmallPreviewCounter, SmallPreviewImages, SmallPreviewLeft, SmallPreviewRight, StackBar, StructuredDataContainer, SvgImage, TILE_ALIGNMENTS, TIME_ZONE_FORMAT, TITLE_SLOT_IDS, TabsContainer, TextTrim, ThemeName, TitleContainer, TopContainer, TopContainerButtons, TwoColumnContainer, UploadContainer, VIEW_MODES, WidgetType, ZOOM_FILTER_NAME, addDataSource, addDataSources, adjustColor, applyFiltersToCondition, applyQueryFilters, applyTreeFilterToCondition, applyVarsToCondition, asAttributeName, asChartId, asContainerId, asDataSourceName, asFilterName, asLayerName, asModalId, asResourceId, asTabId, buildGridTemplate, buildTrackTemplate, checkEqualOrIncludes, checkIsLoading, collectConfigIds, containsNodeId, createConfigLayer, createConfigPage, createGridCell, createGridIdFactory, createGridRow, createNewPageId, createSaveNotificationId, dateOptions, debounce, decimalOpacityToHex, enrichStyleItemsWithIds, enrichStyleModelsWithIds, eqlParametersToPayload, fetchQueryDescription, findAttributeInExpression, findCellContext, formatArea, formatAttributeValue, formatChartRelatedValue, formatConditionValue, formatDataSourceCondition, formatDate$1 as formatDate, formatElementValue, formatLength, formatNumber, formatPolygonMeasure, geometryToEwkt, getActualExtrusionHeight, getAttributeByName, getAttributeConfigurationByName, getAttributeIconElement, getAttributeIconUrl, getAttributeValue, getAttributesConfiguration, getAverageTrackSize, getChartAxes, getChartFilterName, getChartMarkers, getConfigFilter, getContainerComponent, getDashboardHeader, getDataFromAttributes, getDataFromRelatedFeatures, getDataSource, getDataSourceFilterValue, getDataSourceLayerInfo, getDate, getDefaultConfig, getDisplayTemplateNameFromAttribute, getElementValue, getFeatureAttributes, getFeatureCardHeader, getFilterComponent, getFilterSelectedItems, getFilterValue, getFormattedAttributes, getGradientColors, getImageUrl, getLayerClientStyle, getLayerInfo, getLayerInfoAttribute, getLayerInfoFromDataSources, getLayoutChildren, getMapViewDataSources, getPagesFromConfig, getPagesFromProjectInfo, getProxyService, getRelatedAttribute, getRenderElement, getResourceUrl, getRootElementId, getSelectedFilterValue, getSlideshowImages, getStyleAttributes, getSvgUrl, getTemplateNameFromAttribute, getThemeByName, getTotalFromAttributes, getTotalFromRelatedFeatures, getTrackSize, getTrackSizeKey, getTrackSizes, getWrapperSizeStyle, hasContainerBgImage, hexToRgba, isCrossOriginUrl, isEmptyElementValue, isEmptyValue, isFeaturesFilterValue, isFillSize, isFrSize, isGridNode, isHiddenEmptyValue, isHookActive, isLayerService, isNotValidSelectedTab, isNumeric, isObject, isProxyService, isRootOwningContainer, isTreeFilterValue, isVisibleContainer, mapNodeById, mergeAttributeConfigurations, metersPerPixel, noMarginMixin, numberOptions, parseFrValue, parseIconNames, parseIconNamesFromClientStyle, pieChartTooltipFromAttributes, pieChartTooltipFromRelatedFeatures, pointOptions, removeDataSource, removeTracks, replaceNodesByIds, rgbToHex, roundFr, roundTotalSum, setLayoutChildren, sizeCssMixin, sliceShownOtherItems, stretchPalette, timeOptions, toConditionsArray, toCssSize, toFrSize, toPxNumber, toRenderableValue, tooltipNameFromAttributes, tooltipValueFromAttributes, tooltipValueFromRelatedFeatures, transparentizeColor, updateDataSource, useAfterSave, useAppHeight, useAttachmentItems, useAttachmentPreviewImages, useAutoCompleteControl, useBeforeSave, useBgImageHost, useChartChange, useChartData, useContainerAttributes, useContainerRoot, useCurrentPageLayers, useCustomFeatureSelect, useDashboardHeader, useDataSourceLoading, useDataSources, useDebouncedCallback, useDiffPage, useEditGroupAttributes, useEqualTileWidth, useExpandableContainers, useExportPdf, useFeatureSaveHooks, useFetchImageWithAuth, useFetchWithAuth, useGetConfigLayer, useGlobalContext, useHeaderRender, useHideIfEmptyDataSource, useIconsFromLayers, useLayerHiddenAttributes, useLayerParams, useMapContext, useMapDraw, useMapImages, useMaxZoomTo, useProjectDashboardInit, usePythonSandbox, usePythonTask, useRedrawLayer, useRelatedDataSourceAttributes, useRemoteTask, useRenderElement, useResizeBox, useSavePrototypeBuilder, useServerNotificationsContext, useShownOtherItems, useToggle, useUpdateDataSource, useVisibleProjectItems, useWidgetConfig, useWidgetContext, useWidgetFilters, useWidgetPage, useWindowResize, useWrapperSize, useZoomToFeatures, useZoomToPoint, withTrackSize };
|
|
19254
|
+
export { ALIGNMENTS, ALIGN_ITEMS, ATTRIBUTE_ICON_ELEMENT_TYPES, AddButtonRow, AddFeatureButton, AddFeatureContainer, AlertIconContainer, AttachmentContainer, AttributeGalleryContainer, AttributeLabel, BASE_CONTAINER_STYLE, BG_IMAGE_SLOT_ID, BaseMapTheme, CHART_TYPES, COMPACT_FRACTION_DIGITS, CONFIG_PAGES_ID, CONFIG_PAGE_ID, CONTAINERS_GROUP_DEFAULTS, CONTAINER_BODY_ATTRIBUTE, CONTAINER_BODY_FILL_STYLE, CameraContainer, Chart, ChartContainer, ChartLegend, ChartLoading, Container, ContainerBackground, ContainerChildren, ContainerLoading, ContainerRoot, ContainerTemplate, ContainerWrapper, ContainersGroupContainer, DASHBOARD_OVERLAY_Z_INDEX, DEFAULT_ATTRIBUTE_NAME, DEFAULT_BARCHART_RADIUS, DEFAULT_BASE_MAP, DEFAULT_BLUR, DEFAULT_CHART_ANGLE, DEFAULT_CHART_HEIGHT, DEFAULT_CHART_WIDTH, DEFAULT_CIRCLE_RADIUS, DEFAULT_CIRCLE_STROKE_WIDTH, DEFAULT_CIRCLE_STYLE, DEFAULT_COLOR, DEFAULT_DASHBOARD_CONFIG, DEFAULT_DATA_SOURCE_LIMIT, DEFAULT_DROPDOWN_WIDTH, DEFAULT_FILL_EXTRUSION_BASE, DEFAULT_FILL_EXTRUSION_HEIGHT, DEFAULT_FILL_EXTRUSION_STYLE, DEFAULT_FILL_EXTRUSION_VERTICAL_GRADIENT, DEFAULT_FILL_STYLE, DEFAULT_FILTER_PADDING, DEFAULT_GRID_GAP, DEFAULT_HEATMAP_COLOR, DEFAULT_HEATMAP_INTENSITY, DEFAULT_HEATMAP_RADIUS, DEFAULT_HEATMAP_STYLE, DEFAULT_HEATMAP_WEIGHT, DEFAULT_ICON_ANCHOR, DEFAULT_ICON_OVERLAP, DEFAULT_ICON_PADDING, DEFAULT_ICON_ROTATE, DEFAULT_ICON_SIZE, DEFAULT_ID_ATTRIBUTE_NAME, DEFAULT_LAT, DEFAULT_LINE_CAP, DEFAULT_LINE_JOIN, DEFAULT_LINE_STYLE, DEFAULT_LINE_WIDTH, DEFAULT_LNG, DEFAULT_OPACITY, DEFAULT_PAGES_CONFIG, DEFAULT_PIECHART_RADIUS, DEFAULT_SYMBOL_COLOR, DEFAULT_SYMBOL_HALO_COLOR, DEFAULT_SYMBOL_PLACEMENT, DEFAULT_SYMBOL_SPACING, DEFAULT_SYMBOL_STYLE, DEFAULT_TEXT_ANCHOR, DEFAULT_TEXT_JUSTIFY, DEFAULT_TEXT_SIZE, DEFAULT_TEXT_TRANSFORM, DEFAULT_TRACK_FR, DEFAULT_TRANSLATE, DEFAULT_ZOOM, DRAG_THRESHOLD_PX, Dashboard, DashboardCheckbox, DashboardChip, DashboardContent, DashboardContext, DashboardDefaultHeader, DashboardHeader, DashboardLoading, DashboardPlaceholder, DashboardPlaceholderWrap, DashboardProvider, DashboardWrapper, DataSourceContainer, DataSourceError, DataSourceErrorContainer, DataSourceInnerContainer, DataSourceProgressContainer, DateFormat, DefaultAttributesContainer, DefaultHeaderContainer, DefaultHeaderWrapper, DividerContainer, EMPTY_DATA_SOURCE_LAYER_INFO, EXTENT_FILTER_NAME, EditGeometryType, ElementButton, ElementCamera, ElementChart, ElementChips, ElementControl, ElementIcon, ElementImage, ElementLegend, ElementLink, ElementMarkdown, ElementSlideshow, ElementSvg, ElementTable, ElementTooltip, ElementValueWrapper, ExpandableTitle, FEATURE_CARD_DEFAULT_COLORS, FEATURE_CARD_OTHER_COLOR, FILL_SIZE, FILTERED_VALUE_OPACITY, FILTER_PREFIX, FR_PRECISION, FeatureCardBackgroundHeader, FeatureCardButtons, FeatureCardContext, FeatureCardDefaultHeader, FeatureCardHeader, FeatureCardProvider, FeatureCardSlideshowHeader, FeatureCardTitle, FeatureControls, FeatureTitleContainer, FiltersContainer, GEOMETRY_ATTRIBUTE, GEOMETRY_FILTER_NAME, GRID_AUTO_FILL_DEFAULTS, GRID_CELL_ATTR, GRID_CELL_ID_PREFIX, GRID_DRAGGING_ATTR, GRID_DRAG_SOURCE_ATTR, GRID_DROP_TARGET_ATTR, GRID_FILL_DEFAULTS, GRID_HANDLE_ATTR, GRID_HANDLE_PROPS, GRID_ROW_ID_PREFIX, GlobalContext, GlobalProvider, GridRowContainer, HANDLE_BLEED_PX, Header, HeaderContainer, HeaderFontColorMixin, HeaderFrontView, HeaderTemplate, HeaderTitleContainer, HiddenTitleItems, IconContainer, ImageContainer, LEFT_PANEL_HEADER_HEIGHT, Layer, LayerDescription, LayerGroupList, LayerIcon, LayerIconContainer, LayerListContainer, LayerTree, LayersContainer, LayersListWrapper, LinearProgressContainer, LogTerminal, LogoContainer, MAP_VIEW_FILTER_NAMES, MAX_CHART_WIDTH, MAX_TRACKS, MIN_TRACK_PX, MIN_TRACK_RATIO, Map$1 as Map, MapContext, MapProvider, NON_TRACK_SLOT_IDS, NO_CELL_DRAG_SELECTOR, NO_CONTENT_VALUE, NUMERIC_ATTRIBUTE_TYPES, NoLiveSnapshotContainer, OBJECT_FITS, OVERFLOWS, OneColumnContainer, PIE_CHART_TOOLTIP_STYLE, POLL_SUBTASK_INTERVAL_MS, POLL_SUBTASK_TIMEOUT_MS, PROVIDER_PREFIX, PageNavigator, PageTitle, PageTitleContainer, PagesContainer, Pagination, PresentationHeader, PresentationHeaderButtons, PresentationHeaderTools, PresentationPanelContainer, PresentationPanelWrapper, PresentationWrapper, ProgressContainer, ProviderPrefix, QUERY_DESCRIPTION_CACHE_TTL, RoundedBackgroundContainer, SAVE_HOOK_RESULT_DURATION, SERVER_NOTIFICATION_EVENT, STACK_BAR_TOTAL_HEIGHT, ScalingFactor, ServerNotificationsContext, ServerNotificationsProvider, SlideshowContainer, SmallPreviewContainer$1 as SmallPreviewContainer, SmallPreviewControl, SmallPreviewCounter, SmallPreviewImages, SmallPreviewLeft, SmallPreviewRight, StackBar, StructuredDataContainer, SvgImage, TILE_ALIGNMENTS, TIME_ZONE_FORMAT, TITLE_SLOT_IDS, TabsContainer, TextTrim, ThemeName, TitleContainer, TopContainer, TopContainerButtons, TwoColumnContainer, UploadContainer, VIEW_MODES, VoteContainer, WidgetType, ZOOM_FILTER_NAME, addDataSource, addDataSources, adjustColor, applyFiltersToCondition, applyQueryFilters, applyTreeFilterToCondition, applyVarsToCondition, asAttributeName, asChartId, asContainerId, asDataSourceName, asFilterName, asLayerName, asModalId, asResourceId, asTabId, buildGridTemplate, buildTrackTemplate, checkEqualOrIncludes, checkIsLoading, collectConfigIds, containsNodeId, createConfigLayer, createConfigPage, createGridCell, createGridIdFactory, createGridRow, createNewPageId, createSaveNotificationId, dateOptions, debounce, decimalOpacityToHex, enrichStyleItemsWithIds, enrichStyleModelsWithIds, eqlParametersToPayload, fetchQueryDescription, findAttributeInExpression, findCellContext, formatArea, formatAttributeValue, formatChartRelatedValue, formatConditionValue, formatDataSourceCondition, formatDate$1 as formatDate, formatElementValue, formatLength, formatNumber, formatPolygonMeasure, geometryToEwkt, getActualExtrusionHeight, getAttributeByName, getAttributeConfigurationByName, getAttributeIconElement, getAttributeIconUrl, getAttributeValue, getAttributesConfiguration, getAverageTrackSize, getChartAxes, getChartFilterName, getChartMarkers, getConfigFilter, getContainerComponent, getDashboardHeader, getDataFromAttributes, getDataFromRelatedFeatures, getDataSource, getDataSourceFilterValue, getDataSourceLayerInfo, getDate, getDefaultConfig, getDisplayTemplateNameFromAttribute, getElementValue, getFeatureAttributes, getFeatureCardHeader, getFilterComponent, getFilterSelectedItems, getFilterValue, getFormattedAttributes, getGradientColors, getImageUrl, getLayerClientStyle, getLayerInfo, getLayerInfoAttribute, getLayerInfoFromDataSources, getLayoutChildren, getMapViewDataSources, getPagesFromConfig, getPagesFromProjectInfo, getProxyService, getRelatedAttribute, getRenderElement, getResourceUrl, getRootElementId, getSelectedFilterValue, getSlideshowImages, getStyleAttributes, getSvgUrl, getTemplateNameFromAttribute, getThemeByName, getTotalFromAttributes, getTotalFromRelatedFeatures, getTrackSize, getTrackSizeKey, getTrackSizes, getWrapperSizeStyle, hasContainerBgImage, hexToRgba, isCrossOriginUrl, isEmptyElementValue, isEmptyValue, isFeaturesFilterValue, isFillSize, isFrSize, isGridNode, isHiddenEmptyValue, isHookActive, isLayerService, isNotValidSelectedTab, isNumeric, isObject, isProxyService, isRootOwningContainer, isTreeFilterValue, isVisibleContainer, mapNodeById, mergeAttributeConfigurations, metersPerPixel, noMarginMixin, numberOptions, parseFrValue, parseIconNames, parseIconNamesFromClientStyle, pieChartTooltipFromAttributes, pieChartTooltipFromRelatedFeatures, pointOptions, removeDataSource, removeTracks, replaceNodesByIds, rgbToHex, roundFr, roundTotalSum, setLayoutChildren, sizeCssMixin, sliceShownOtherItems, stretchPalette, timeOptions, toConditionsArray, toCssSize, toFrSize, toPxNumber, toRenderableValue, tooltipNameFromAttributes, tooltipValueFromAttributes, tooltipValueFromRelatedFeatures, transparentizeColor, updateDataSource, useAfterSave, useAppHeight, useAttachmentDownload, useAttachmentItems, useAttachmentPreviewImages, useAutoCompleteControl, useBeforeSave, useBgImageHost, useChartChange, useChartData, useContainerAttributes, useContainerRoot, useCurrentPageLayers, useCustomFeatureSelect, useDashboardHeader, useDataSourceLoading, useDataSources, useDebouncedCallback, useDiffPage, useEditGroupAttributes, useEqualTileWidth, useExpandableContainers, useExportPdf, useFeatureSaveHooks, useFetchImageWithAuth, useFetchWithAuth, useGetConfigLayer, useGlobalContext, useHeaderRender, useHideIfEmptyDataSource, useIconsFromLayers, useLayerHiddenAttributes, useLayerParams, useMapContext, useMapDraw, useMapImages, useMaxZoomTo, useProjectDashboardInit, usePythonSandbox, usePythonTask, useRedrawLayer, useRelatedDataSourceAttributes, useRemoteTask, useRenderElement, useResizeBox, useSavePrototypeBuilder, useServerNotificationsContext, useShownOtherItems, useToggle, useUpdateDataSource, useVisibleProjectItems, useWidgetConfig, useWidgetContext, useWidgetFilters, useWidgetPage, useWindowResize, useWrapperSize, useZoomToFeatures, useZoomToPoint, withTrackSize };
|
|
18458
19255
|
//# sourceMappingURL=react.esm.js.map
|