@evergis/react 4.0.149 → 4.0.151
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/containers/AttachmentContainer/components/ShowMoreButton.d.ts +3 -0
- package/dist/components/Dashboard/containers/AttachmentContainer/styled.d.ts +6 -0
- package/dist/components/Dashboard/containers/AttachmentContainer/useAttachmentContainer.d.ts +3 -9
- package/dist/components/Dashboard/containers/StructuredDataContainer/types.d.ts +4 -0
- package/dist/components/Dashboard/elements/ElementModal/hooks/useModalOpen.d.ts +11 -0
- package/dist/components/Dashboard/elements/ElementTable/components/AttachmentsCell.d.ts +4 -1
- package/dist/components/Dashboard/elements/ElementTable/components/AttachmentsCellPopup/index.d.ts +10 -0
- package/dist/components/Dashboard/elements/ElementTable/components/AttachmentsCellPopup/styled.d.ts +9 -0
- package/dist/components/Dashboard/elements/ElementTable/constants.d.ts +12 -0
- package/dist/components/Dashboard/elements/ElementTable/hooks/useAttachmentsCell.d.ts +5 -20
- package/dist/components/Dashboard/elements/ElementTable/hooks/useAttachmentsCellEdit.d.ts +28 -0
- package/dist/components/Dashboard/elements/ElementTable/styled.d.ts +17 -1
- package/dist/components/Dashboard/elements/ElementTable/types.d.ts +44 -0
- package/dist/components/Dashboard/hooks/index.d.ts +2 -0
- package/dist/components/Dashboard/hooks/useAttachmentsView.d.ts +25 -0
- package/dist/components/Dashboard/hooks/useConfigDataSources.d.ts +7 -0
- package/dist/components/Dashboard/hooks/useWidgetContext.d.ts +1 -0
- package/dist/components/Dashboard/types.d.ts +11 -0
- package/dist/components/Dashboard/utils/getModalsDataSources.d.ts +6 -0
- package/dist/components/Dashboard/utils/index.d.ts +1 -0
- package/dist/components/Dashboard/utils/interpolateTranslation.d.ts +8 -0
- package/dist/components/Dashboard/utils/sliceShownOtherItems.d.ts +5 -0
- package/dist/contexts/DashboardContext/types.d.ts +6 -0
- package/dist/contexts/FeatureCardContext/types.d.ts +2 -0
- package/dist/index.js +410 -70
- package/dist/index.js.map +1 -1
- package/dist/react.esm.js +408 -72
- package/dist/react.esm.js.map +1 -1
- package/package.json +2 -2
- package/dist/components/Dashboard/grid/components/GridResizer/styled.d.ts +0 -11
package/dist/index.js
CHANGED
|
@@ -5596,12 +5596,22 @@ const ServerNotificationsProvider = ({ url, initialized, apiClient, children })
|
|
|
5596
5596
|
}, children: children }));
|
|
5597
5597
|
};
|
|
5598
5598
|
|
|
5599
|
+
const INTERPOLATION_PATTERN = /\{\{\s*(\w+)\s*\}\}/g;
|
|
5600
|
+
/**
|
|
5601
|
+
* Подставляет в строку перевода переменные вида `{{total}}` — так, как это делает i18next.
|
|
5602
|
+
*
|
|
5603
|
+
* Нужна запасному переводчику, когда хост не передал свой `t` (например, в Storybook): без неё
|
|
5604
|
+
* `defaultValue` с переменными уходит в интерфейс как есть. Переменная без значения остаётся
|
|
5605
|
+
* в тексте, чтобы пропуск был виден.
|
|
5606
|
+
*/
|
|
5607
|
+
const interpolateTranslation = (text, values = {}) => text.replace(INTERPOLATION_PATTERN, (match, name) => values[name] === undefined || values[name] === null ? match : String(values[name]));
|
|
5608
|
+
|
|
5599
5609
|
const useGlobalContext = () => {
|
|
5600
5610
|
const { t, language, themeName, api, ewktGeometry, ewktExtent, zoomLevel, projectName, projectAlias, notification } = React.useContext(GlobalContext) || {};
|
|
5601
5611
|
const translate = React.useCallback((value, options) => {
|
|
5602
5612
|
if (t)
|
|
5603
5613
|
return t(value, options);
|
|
5604
|
-
return options?.defaultValue ?? value;
|
|
5614
|
+
return interpolateTranslation(options?.defaultValue ?? value, options);
|
|
5605
5615
|
}, [t]);
|
|
5606
5616
|
return React.useMemo(() => ({
|
|
5607
5617
|
t: translate,
|
|
@@ -5742,8 +5752,8 @@ const useAttachmentDownload = (items) => {
|
|
|
5742
5752
|
* {@link FeatureCardContext} на пересекающуюся базу и виджет-специфичные расширения.
|
|
5743
5753
|
*/
|
|
5744
5754
|
const useWidgetContext = (type = exports.WidgetType.Dashboard) => {
|
|
5745
|
-
const { toggleLayersVisibility, visibleLayers, projectInfo, updateProject, layerInfos, geometryFilter, dashboardLayers, setDashboardLayer, components: dashboardComponents, selectAttachmentsFromCatalog, config: dashboardConfig, containerIds, pageIndex: projectPageIndex, selectedTabId: projectSelectedTabId, setSelectedTabId: setProjectSelectedTabId, dataSources: projectDataSources, loading: projectLoading, editMode: projectEditMode, onContainerChange: projectOnContainerChange, filters: projectFilters, changeFilters: projectChangeFilters, expandContainer: projectExpandContainer, expandedContainers: projectExpandedContainers, nextPage: projectNextPage, prevPage: projectPrevPage, changePage: projectChangePage, } = React.useContext(DashboardContext) || {};
|
|
5746
|
-
const { layerInfo, attributes, feature, controls, changeControls, closeFeatureCard, config: featureConfig, pageIndex: featurePageIndex, selectedTabId: featureSelectedTabId, setSelectedTabId: setFeatureSelectedTabId, dataSources: featureDataSources, loading: featureLoading, editMode: featureEditMode, onContainerChange: featureOnContainerChange, filters: featureFilters, changeFilters: featureChangeFilters, expandContainer: featureExpandContainer, expandedContainers: featureExpandedContainers, nextPage: featureNextPage, prevPage: featurePrevPage, changePage: featureChangePage, } = React.useContext(FeatureCardContext) || {};
|
|
5755
|
+
const { toggleLayersVisibility, visibleLayers, projectInfo, updateProject, layerInfos, geometryFilter, dashboardLayers, setDashboardLayer, components: dashboardComponents, selectAttachmentsFromCatalog, config: dashboardConfig, containerIds, pageIndex: projectPageIndex, selectedTabId: projectSelectedTabId, setSelectedTabId: setProjectSelectedTabId, dataSources: projectDataSources, loading: projectLoading, editMode: projectEditMode, onContainerChange: projectOnContainerChange, onModalToggle: projectOnModalToggle, filters: projectFilters, changeFilters: projectChangeFilters, expandContainer: projectExpandContainer, expandedContainers: projectExpandedContainers, nextPage: projectNextPage, prevPage: projectPrevPage, changePage: projectChangePage, } = React.useContext(DashboardContext) || {};
|
|
5756
|
+
const { layerInfo, attributes, feature, controls, changeControls, closeFeatureCard, config: featureConfig, pageIndex: featurePageIndex, selectedTabId: featureSelectedTabId, setSelectedTabId: setFeatureSelectedTabId, dataSources: featureDataSources, loading: featureLoading, editMode: featureEditMode, onContainerChange: featureOnContainerChange, onModalToggle: featureOnModalToggle, filters: featureFilters, changeFilters: featureChangeFilters, expandContainer: featureExpandContainer, expandedContainers: featureExpandedContainers, nextPage: featureNextPage, prevPage: featurePrevPage, changePage: featureChangePage, } = React.useContext(FeatureCardContext) || {};
|
|
5747
5757
|
return {
|
|
5748
5758
|
toggleLayersVisibility,
|
|
5749
5759
|
visibleLayers,
|
|
@@ -5765,6 +5775,7 @@ const useWidgetContext = (type = exports.WidgetType.Dashboard) => {
|
|
|
5765
5775
|
config: type === exports.WidgetType.Dashboard ? dashboardConfig : featureConfig,
|
|
5766
5776
|
isEditing: type === exports.WidgetType.Dashboard ? projectEditMode : featureEditMode,
|
|
5767
5777
|
onContainerChange: type === exports.WidgetType.Dashboard ? projectOnContainerChange : featureOnContainerChange,
|
|
5778
|
+
onModalToggle: type === exports.WidgetType.Dashboard ? projectOnModalToggle : featureOnModalToggle,
|
|
5768
5779
|
isLoading: type === exports.WidgetType.Dashboard ? projectLoading : featureLoading,
|
|
5769
5780
|
pageIndex: type === exports.WidgetType.Dashboard ? projectPageIndex || 1 : featurePageIndex || 1,
|
|
5770
5781
|
filters: type === exports.WidgetType.Dashboard ? projectFilters : featureFilters,
|
|
@@ -6027,6 +6038,30 @@ const useAttachmentPreviewImages = ({ items, active, }) => {
|
|
|
6027
6038
|
}), [items, blobUrls, failedLinks]);
|
|
6028
6039
|
};
|
|
6029
6040
|
|
|
6041
|
+
/**
|
|
6042
|
+
* Вид списка вложений: плитка или строки и сколько файлов из списка показано.
|
|
6043
|
+
*
|
|
6044
|
+
* Общий для контейнера вложений и колонки таблицы: список у них один и тот же, и считать
|
|
6045
|
+
* видимое дважды незачем. Предел приходит числом, а не опциями конфига, — у колонки опций
|
|
6046
|
+
* вида нет, он у неё зашит константой.
|
|
6047
|
+
*/
|
|
6048
|
+
const useAttachmentsView = ({ items, limit, initialViewMode = "grid", }) => {
|
|
6049
|
+
const [viewMode, setViewMode] = React.useState(initialViewMode);
|
|
6050
|
+
const [showMore, setShowMore] = React.useState(false);
|
|
6051
|
+
const visibleItems = React.useMemo(() => (limit && !showMore ? items.slice(0, limit) : items), [items, limit, showMore]);
|
|
6052
|
+
const hiddenCount = items.length - visibleItems.length;
|
|
6053
|
+
const handleSetViewMode = React.useCallback((mode) => setViewMode(mode), []);
|
|
6054
|
+
return {
|
|
6055
|
+
visibleItems,
|
|
6056
|
+
hiddenCount,
|
|
6057
|
+
hasMore: hiddenCount > 0,
|
|
6058
|
+
showMore,
|
|
6059
|
+
setShowMore,
|
|
6060
|
+
viewMode,
|
|
6061
|
+
setViewMode: handleSetViewMode,
|
|
6062
|
+
};
|
|
6063
|
+
};
|
|
6064
|
+
|
|
6030
6065
|
const useAutoCompleteControl = (items) => {
|
|
6031
6066
|
const [value, setValue] = React.useState("");
|
|
6032
6067
|
const [options, setOptions] = React.useState([]);
|
|
@@ -7193,6 +7228,14 @@ const useChartChange = ({ dataSources, chartId, width, height, relatedAttributes
|
|
|
7193
7228
|
return [customize, onChange];
|
|
7194
7229
|
};
|
|
7195
7230
|
|
|
7231
|
+
/**
|
|
7232
|
+
* Источники модалок конфига (`config.modals[].dataSources`) без дублей по имени — выигрывает первый.
|
|
7233
|
+
* Без `modalIds` — источники всех модалок, иначе только перечисленных.
|
|
7234
|
+
*/
|
|
7235
|
+
const getModalsDataSources = (config, modalIds) => lodash.uniqBy((config?.modals ?? [])
|
|
7236
|
+
.filter(({ id }) => !modalIds || modalIds.includes(id))
|
|
7237
|
+
.flatMap(({ dataSources }) => dataSources ?? []), "name");
|
|
7238
|
+
|
|
7196
7239
|
/**
|
|
7197
7240
|
* Generic-параметр `T` сейчас идёт как намерение (под какой виджет). Дальнейшее сужение
|
|
7198
7241
|
* `config` под конкретный виджет — отдельная задача.
|
|
@@ -7319,15 +7362,26 @@ const useWidgetPage = (type = exports.WidgetType.Dashboard) => {
|
|
|
7319
7362
|
};
|
|
7320
7363
|
};
|
|
7321
7364
|
|
|
7365
|
+
/**
|
|
7366
|
+
* Конфиги всех источников, доступных контейнерам виджета: страница с корнем и все модалки.
|
|
7367
|
+
* Одноимённый источник модалки перекрывается страничным. Только для поиска по имени — в конфиг
|
|
7368
|
+
* страницы не пишется: `currentPage` сохраняется обратно целиком, и модальные источники осели бы в нём.
|
|
7369
|
+
*/
|
|
7370
|
+
const useConfigDataSources = (type = exports.WidgetType.Dashboard) => {
|
|
7371
|
+
const { config } = useWidgetConfig(type);
|
|
7372
|
+
const { currentPage } = useWidgetPage(type);
|
|
7373
|
+
return React.useMemo(() => lodash.uniqBy([...(currentPage?.dataSources ?? []), ...getModalsDataSources(config)], "name"), [config, currentPage?.dataSources]);
|
|
7374
|
+
};
|
|
7375
|
+
|
|
7322
7376
|
const useChartData = ({ element, type }) => {
|
|
7323
7377
|
const { t } = useGlobalContext();
|
|
7324
7378
|
const { dataSources, layerInfos, attributes } = useWidgetContext(type);
|
|
7325
7379
|
const { currentPage } = useWidgetPage(type);
|
|
7380
|
+
const configDataSources = useConfigDataSources(type);
|
|
7326
7381
|
const relatedAttributes = React.useMemo(() => element?.options?.relatedDataSources || [], [element?.options?.relatedDataSources]);
|
|
7327
7382
|
const loading = React.useMemo(() => !!relatedAttributes?.length &&
|
|
7328
7383
|
!dataSources?.some(({ name }) => relatedAttributes.some(({ chartAxis, dataSourceName }) => chartAxis === "y" && dataSourceName === name)), [dataSources, relatedAttributes]);
|
|
7329
7384
|
const fetchedData = React.useMemo(() => {
|
|
7330
|
-
const configDataSources = currentPage?.dataSources || [];
|
|
7331
7385
|
const isRelated = !!relatedAttributes?.length;
|
|
7332
7386
|
const filteredAttributes = relatedAttributes.filter(({ chartAxis }) => chartAxis === "y");
|
|
7333
7387
|
if (!element)
|
|
@@ -7378,7 +7432,7 @@ const useChartData = ({ element, type }) => {
|
|
|
7378
7432
|
},
|
|
7379
7433
|
];
|
|
7380
7434
|
}, [
|
|
7381
|
-
|
|
7435
|
+
configDataSources,
|
|
7382
7436
|
currentPage?.filters,
|
|
7383
7437
|
relatedAttributes,
|
|
7384
7438
|
element,
|
|
@@ -8247,11 +8301,11 @@ const useResizeDrag = ({ onStart, onMove, onEnd }) => {
|
|
|
8247
8301
|
|
|
8248
8302
|
const useRelatedDataSourceAttributes = ({ type = exports.WidgetType.Dashboard, elementConfig, dataSources, feature, }) => {
|
|
8249
8303
|
const { layerInfos } = useWidgetContext(type);
|
|
8250
|
-
const
|
|
8304
|
+
const configDataSources = useConfigDataSources(type);
|
|
8251
8305
|
const { options } = elementConfig || {};
|
|
8252
8306
|
const { relatedDataSource } = options || {};
|
|
8253
8307
|
const dataSource = React.useMemo(() => getDataSource(relatedDataSource, dataSources), [dataSources, relatedDataSource]);
|
|
8254
|
-
const configDataSource = React.useMemo(() =>
|
|
8308
|
+
const configDataSource = React.useMemo(() => configDataSources.find(({ name }) => name === relatedDataSource), [configDataSources, relatedDataSource]);
|
|
8255
8309
|
const layerInfo = React.useMemo(() => getDataSourceLayerInfo({ layerInfos, configDataSource, fetchedDataSource: dataSource }) ||
|
|
8256
8310
|
EMPTY_DATA_SOURCE_LAYER_INFO, [configDataSource, dataSource, layerInfos]);
|
|
8257
8311
|
const attributes = React.useMemo(() => getFeatureAttributes(feature, layerInfo, dataSource), [dataSource, feature, layerInfo]);
|
|
@@ -8567,9 +8621,23 @@ const AttachmentsViewControls = styled.div.withConfig({ displayName: "Attachment
|
|
|
8567
8621
|
font-size: 1rem;
|
|
8568
8622
|
}
|
|
8569
8623
|
`;
|
|
8624
|
+
/**
|
|
8625
|
+
* Счётчик файлов у подписи — плоский чип в строку подписи.
|
|
8626
|
+
*
|
|
8627
|
+
* Шрифт ставится на текст чипа, а не на корень: текст задаёт себе шрифт шорткатом `font` из темы,
|
|
8628
|
+
* и размер с корня до цифры не доходит — чип выходил высоким, во весь шрифт описания.
|
|
8629
|
+
*/
|
|
8570
8630
|
const AttachmentsCountChip = styled(uilibGl.Chip).withConfig({ displayName: "AttachmentsCountChip", componentId: "sc-117ib6u" }) `
|
|
8571
|
-
|
|
8572
|
-
|
|
8631
|
+
&& {
|
|
8632
|
+
padding: 0.0625rem 0.25rem;
|
|
8633
|
+
border-radius: 0.75rem;
|
|
8634
|
+
}
|
|
8635
|
+
|
|
8636
|
+
span {
|
|
8637
|
+
font-size: 0.625rem;
|
|
8638
|
+
line-height: 0.75rem;
|
|
8639
|
+
font-weight: bold;
|
|
8640
|
+
}
|
|
8573
8641
|
`;
|
|
8574
8642
|
const AttachmentsContent = styled.div.withConfig({ displayName: "AttachmentsContent", componentId: "sc-1j2thff" }) `
|
|
8575
8643
|
width: 100%;
|
|
@@ -8852,38 +8920,30 @@ const AttachmentsList = ({ items, isEdit, onPreview, onDelete, }) => {
|
|
|
8852
8920
|
return (jsxRuntime.jsx(ListContainer, { children: items.map(item => (jsxRuntime.jsx(AttachmentItem, { item: item, viewMode: "list", isEdit: isEdit, onPreview: onPreview, onDelete: onDelete }, item.link))) }));
|
|
8853
8921
|
};
|
|
8854
8922
|
|
|
8855
|
-
const ShowMoreButton = ({ hiddenCount, onClick, }) => {
|
|
8923
|
+
const ShowMoreButton = ({ hiddenCount, iconKind = "arrow_down", onClick, }) => {
|
|
8856
8924
|
const { t } = useGlobalContext();
|
|
8857
|
-
return (jsxRuntime.jsxs(ShowMoreButton$1, { onClick: onClick, children: [t("showMore", { ns: "dashboard", defaultValue: "Показать ещё" }), " ", hiddenCount, jsxRuntime.jsx(uilibGl.Icon, { kind:
|
|
8925
|
+
return (jsxRuntime.jsxs(ShowMoreButton$1, { onClick: onClick, children: [t("showMore", { ns: "dashboard", defaultValue: "Показать ещё" }), " ", hiddenCount, jsxRuntime.jsx(uilibGl.Icon, { kind: iconKind })] }));
|
|
8858
8926
|
};
|
|
8859
8927
|
|
|
8928
|
+
/**
|
|
8929
|
+
* Сколько элементов показывать сразу. Заданы обе опции — выигрывает меньшая, не задана ни одна —
|
|
8930
|
+
* предела нет и список показывается целиком.
|
|
8931
|
+
*/
|
|
8932
|
+
const getShownItemsLimit = ({ shownItems, otherItems } = {}) => shownItems && otherItems ? Math.min(shownItems, otherItems) : shownItems || otherItems;
|
|
8860
8933
|
const sliceShownOtherItems = (data, options = {}, showMore) => {
|
|
8861
|
-
const
|
|
8862
|
-
|
|
8863
|
-
return (shownItems || otherItems) && !showMore ? (data?.slice(0, limit) || []) : data;
|
|
8934
|
+
const limit = getShownItemsLimit(options);
|
|
8935
|
+
return limit && !showMore ? (data?.slice(0, limit) || []) : data;
|
|
8864
8936
|
};
|
|
8865
8937
|
|
|
8866
8938
|
const useAttachmentContainer = ({ type, elementConfig, valueOverride, }) => {
|
|
8867
8939
|
const { items, attributeName } = useAttachmentItems({ type, elementConfig, valueOverride });
|
|
8868
8940
|
const { options } = elementConfig || {};
|
|
8869
|
-
const
|
|
8870
|
-
const [viewMode, setViewMode] = React.useState(initialViewMode);
|
|
8871
|
-
const [showMore, setShowMore] = React.useState(false);
|
|
8872
|
-
const visibleItems = React.useMemo(() => sliceShownOtherItems(items, options, showMore), [items, options, showMore]);
|
|
8873
|
-
const hiddenCount = items.length - visibleItems.length;
|
|
8874
|
-
const hasMore = hiddenCount > 0;
|
|
8875
|
-
const handleSetViewMode = React.useCallback((mode) => setViewMode(mode), []);
|
|
8876
|
-
return {
|
|
8941
|
+
const view = useAttachmentsView({
|
|
8877
8942
|
items,
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
setShowMore,
|
|
8883
|
-
viewMode,
|
|
8884
|
-
setViewMode: handleSetViewMode,
|
|
8885
|
-
attributeName,
|
|
8886
|
-
};
|
|
8943
|
+
limit: getShownItemsLimit(options),
|
|
8944
|
+
initialViewMode: options?.viewMode === "list" ? "list" : "grid",
|
|
8945
|
+
});
|
|
8946
|
+
return { ...view, items, attributeName };
|
|
8887
8947
|
};
|
|
8888
8948
|
|
|
8889
8949
|
const AttachmentContainer = React.memo(({ type, elementConfig, renderElement }) => {
|
|
@@ -11488,6 +11548,9 @@ const toSchemaAttribute = (params) => {
|
|
|
11488
11548
|
multiline: description?.multiline,
|
|
11489
11549
|
colorPicker: description?.colorPicker,
|
|
11490
11550
|
style: description?.style,
|
|
11551
|
+
// Настройки загрузки файлов в колонку вложений: у источника их тоже нет.
|
|
11552
|
+
parentResourceId: description?.parentResourceId,
|
|
11553
|
+
fileExtensions: description?.fileExtensions,
|
|
11491
11554
|
};
|
|
11492
11555
|
};
|
|
11493
11556
|
/**
|
|
@@ -13778,6 +13841,8 @@ const AddButton = ({ multiple = true, accept, onSelectFiles, onSelectFromCatalog
|
|
|
13778
13841
|
value: AddAttachmentSource.Catalog,
|
|
13779
13842
|
icon: "folder_outline",
|
|
13780
13843
|
text: t("attachments.fromCatalog", { ns: "common", defaultValue: "Из каталога" }),
|
|
13844
|
+
// Диалог каталога выдаёт виджету приложение: не выдан — и выбирать нечем.
|
|
13845
|
+
disabled: !onSelectFromCatalog,
|
|
13781
13846
|
},
|
|
13782
13847
|
{
|
|
13783
13848
|
value: AddAttachmentSource.Link,
|
|
@@ -13785,7 +13850,7 @@ const AddButton = ({ multiple = true, accept, onSelectFiles, onSelectFromCatalog
|
|
|
13785
13850
|
text: t("attachments.fromLink", { ns: "common", defaultValue: "По ссылке" }),
|
|
13786
13851
|
disabled: !onSelectFromLink,
|
|
13787
13852
|
},
|
|
13788
|
-
], [t, onSelectFromLink]);
|
|
13853
|
+
], [t, onSelectFromCatalog, onSelectFromLink]);
|
|
13789
13854
|
const handleMenuSelect = React.useCallback(([{ value }]) => {
|
|
13790
13855
|
if (value === AddAttachmentSource.Pc) {
|
|
13791
13856
|
inputRef.current?.click();
|
|
@@ -15500,6 +15565,92 @@ const ElementSvg = React.memo(({ type, elementConfig, ...rest }) => {
|
|
|
15500
15565
|
return (jsxRuntime.jsx(SvgImage, { url: getSvgUrl({ elementConfig, layerInfo, attributes }), width: width, height: height, fontColor: fontColor }));
|
|
15501
15566
|
});
|
|
15502
15567
|
|
|
15568
|
+
/**
|
|
15569
|
+
* Вложения ячейки в поп-апе. Ширина своя, а не по ячейке: колонка таблицы бывает узкой,
|
|
15570
|
+
* а внутри стоит полный список вложений с плиткой и кнопкой добавления.
|
|
15571
|
+
*
|
|
15572
|
+
* Список взят у контейнера вложений карточки, а величины у поп-апа свои, с макета: имя файла
|
|
15573
|
+
* крупнее и ссылочного цвета, значок в строку имени, «Показать ещё» серое. Переопределены они
|
|
15574
|
+
* здесь, а не в самих компонентах, чтобы вложения в карточке объекта остались как были.
|
|
15575
|
+
*/
|
|
15576
|
+
const AttachmentsPopupBox = styled.div.withConfig({ displayName: "AttachmentsPopupBox", componentId: "sc-1dv1wz6" }) `
|
|
15577
|
+
display: flex;
|
|
15578
|
+
flex-direction: column;
|
|
15579
|
+
box-sizing: border-box;
|
|
15580
|
+
width: 19.5rem;
|
|
15581
|
+
padding: 1.25rem;
|
|
15582
|
+
|
|
15583
|
+
${AttachmentsLabel} {
|
|
15584
|
+
gap: 0 0.25rem;
|
|
15585
|
+
line-height: 0.875rem;
|
|
15586
|
+
}
|
|
15587
|
+
|
|
15588
|
+
${AttachmentsViewControls} {
|
|
15589
|
+
gap: 0 0.5rem;
|
|
15590
|
+
|
|
15591
|
+
${uilibGl.IconToggleButton}, ${uilibGl.Icon} {
|
|
15592
|
+
width: 0.875rem;
|
|
15593
|
+
height: 0.875rem;
|
|
15594
|
+
}
|
|
15595
|
+
|
|
15596
|
+
${uilibGl.Icon}:after {
|
|
15597
|
+
font-size: 0.875rem;
|
|
15598
|
+
}
|
|
15599
|
+
}
|
|
15600
|
+
|
|
15601
|
+
${ListItemMeta} {
|
|
15602
|
+
${ListIcon}, ${ImagePreviewContainer}, ${GridImagePreview} {
|
|
15603
|
+
width: 1rem;
|
|
15604
|
+
height: 1rem;
|
|
15605
|
+
background-size: contain;
|
|
15606
|
+
}
|
|
15607
|
+
}
|
|
15608
|
+
|
|
15609
|
+
${ListItemDescription} {
|
|
15610
|
+
gap: 0;
|
|
15611
|
+
}
|
|
15612
|
+
|
|
15613
|
+
${ListItemName} {
|
|
15614
|
+
font-size: 1rem;
|
|
15615
|
+
line-height: 1.125rem;
|
|
15616
|
+
color: ${({ theme }) => theme.palette.primary};
|
|
15617
|
+
}
|
|
15618
|
+
|
|
15619
|
+
${ListItemDate} {
|
|
15620
|
+
font-size: 0.75rem;
|
|
15621
|
+
line-height: 0.875rem;
|
|
15622
|
+
}
|
|
15623
|
+
|
|
15624
|
+
${ShowMoreButton$1} {
|
|
15625
|
+
gap: 0 0.25rem;
|
|
15626
|
+
margin-top: 0.5rem;
|
|
15627
|
+
line-height: 0.875rem;
|
|
15628
|
+
color: ${({ theme }) => theme.palette.textSecondary};
|
|
15629
|
+
|
|
15630
|
+
${uilibGl.Icon} {
|
|
15631
|
+
width: 0.875rem;
|
|
15632
|
+
height: 0.875rem;
|
|
15633
|
+
}
|
|
15634
|
+
|
|
15635
|
+
${uilibGl.Icon}:after {
|
|
15636
|
+
font-size: 0.875rem;
|
|
15637
|
+
color: ${({ theme }) => theme.palette.textSecondary};
|
|
15638
|
+
}
|
|
15639
|
+
}
|
|
15640
|
+
`;
|
|
15641
|
+
|
|
15642
|
+
/**
|
|
15643
|
+
* Полный список вложений колонки — тот же набор, что у контейнера вложений в карточке объекта:
|
|
15644
|
+
* шапка с подписью, счётчиком и переключателем вида, сам список, «Показать ещё» и добавление.
|
|
15645
|
+
*
|
|
15646
|
+
* Кнопка добавления и корзины у файлов появляются только на правку: на чтение поп-ап нужен
|
|
15647
|
+
* ровно затем, чтобы развернуть свёрнутый счётчик.
|
|
15648
|
+
*/
|
|
15649
|
+
const AttachmentsCellPopup = React.memo(({ alias, state }) => {
|
|
15650
|
+
const { items, visibleItems, editable, viewMode, setViewMode, hasMore, hiddenCount, onShowMore, accept, onPreview, onDelete, onUpload, onSelectFromCatalog, onOpenLinkDialog, } = state;
|
|
15651
|
+
return (jsxRuntime.jsxs(AttachmentsPopupBox, { children: [jsxRuntime.jsx(AttachmentsHeader, { alias: alias, count: items.length, viewMode: viewMode, onChangeViewMode: setViewMode }), jsxRuntime.jsx(AttachmentsContent, { children: viewMode === "grid" ? (jsxRuntime.jsx(AttachmentsGrid, { items: visibleItems, isEdit: editable, onPreview: onPreview, onDelete: onDelete })) : (jsxRuntime.jsx(AttachmentsList, { items: visibleItems, isEdit: editable, onPreview: onPreview, onDelete: onDelete })) }), hasMore && jsxRuntime.jsx(ShowMoreButton, { hiddenCount: hiddenCount, iconKind: "expand", onClick: onShowMore }), editable && (jsxRuntime.jsx(AddButton, { accept: accept, onSelectFiles: onUpload, onSelectFromCatalog: onSelectFromCatalog, onSelectFromLink: onOpenLinkDialog }))] }));
|
|
15652
|
+
});
|
|
15653
|
+
|
|
15503
15654
|
/**
|
|
15504
15655
|
* Насколько тело таблицы заехало под липкую шапку. Пишется на таблицу при прокрутке, читается
|
|
15505
15656
|
* стилем тела: ровно на эту величину тело и отсекается.
|
|
@@ -15525,6 +15676,18 @@ const MIN_COLUMN_WIDTH = 48;
|
|
|
15525
15676
|
* без валидной строки не собрать.
|
|
15526
15677
|
*/
|
|
15527
15678
|
const DEFAULT_CELL_COLOR = "#000000";
|
|
15679
|
+
/**
|
|
15680
|
+
* Сколько вложений ячейка показывает сама. Больше — все схлопываются в одну строку со значком
|
|
15681
|
+
* ссылки и счётчиком: колонка в таблице узкая, и списком там помещается ровно один файл.
|
|
15682
|
+
*/
|
|
15683
|
+
const ATTACHMENTS_INLINE_LIMIT = 1;
|
|
15684
|
+
/**
|
|
15685
|
+
* Сколько файлов видно в поп-апе вложений сразу, остальные прячутся за «Показать ещё N».
|
|
15686
|
+
* Величина с макета и опцией не выносится: поп-ап у колонки один на все таблицы.
|
|
15687
|
+
*/
|
|
15688
|
+
const ATTACHMENTS_SHOWN_ITEMS = 3;
|
|
15689
|
+
/** Вид списка, с которого поп-ап вложений открывается. */
|
|
15690
|
+
const ATTACHMENTS_VIEW_MODE = "list";
|
|
15528
15691
|
|
|
15529
15692
|
/**
|
|
15530
15693
|
* Собственный бокс таблицы. Без размеров в `options` представления узел ничего не меняет:
|
|
@@ -15751,10 +15914,42 @@ const CellField = styled.div.withConfig({ displayName: "CellField", componentId:
|
|
|
15751
15914
|
*/
|
|
15752
15915
|
const AttachmentsCellBox = styled.div.withConfig({ displayName: "AttachmentsCellBox", componentId: "sc-1ayq7sq" }) `
|
|
15753
15916
|
display: flex;
|
|
15754
|
-
|
|
15755
|
-
|
|
15756
|
-
gap: 0.25rem;
|
|
15917
|
+
align-items: center;
|
|
15918
|
+
gap: 0.375rem;
|
|
15757
15919
|
padding: 0.375rem 0.25rem;
|
|
15920
|
+
min-width: 0;
|
|
15921
|
+
line-height: 1.25rem;
|
|
15922
|
+
/* Пустая ячейка на чтении не делает по клику ничего — и на указатель ей меняться незачем. */
|
|
15923
|
+
cursor: ${({ $interactive }) => ($interactive ? "pointer" : "default")};
|
|
15924
|
+
`;
|
|
15925
|
+
/**
|
|
15926
|
+
* Значок файла в ячейке. Тот же, что в списке вложений, только с макетную величину строки
|
|
15927
|
+
* таблицы: в контейнере строка свободнее и значок там крупнее.
|
|
15928
|
+
*/
|
|
15929
|
+
const AttachmentsCellIcon = styled(ListIcon).withConfig({ displayName: "AttachmentsCellIcon", componentId: "sc-wf195o" }) `
|
|
15930
|
+
width: 1rem;
|
|
15931
|
+
height: 1rem;
|
|
15932
|
+
background-size: contain;
|
|
15933
|
+
`;
|
|
15934
|
+
/**
|
|
15935
|
+
* Подпись файла или счётчика свёрнутых. Ссылочная: по ней и кликают — одиночный файл открывает
|
|
15936
|
+
* галерею, счётчик разворачивает список.
|
|
15937
|
+
*/
|
|
15938
|
+
const AttachmentsCellLabel = styled.div.withConfig({ displayName: "AttachmentsCellLabel", componentId: "sc-11wbjsl" }) `
|
|
15939
|
+
color: ${({ theme }) => theme.palette.primary};
|
|
15940
|
+
|
|
15941
|
+
${cellWrapMixin};
|
|
15942
|
+
`;
|
|
15943
|
+
/** Значок ссылки у счётчика свёрнутых файлов — в цвет подписи. */
|
|
15944
|
+
const AttachmentsCellLinkIcon = styled(uilibGl.Icon).withConfig({ displayName: "AttachmentsCellLinkIcon", componentId: "sc-oxcx2o" }) `
|
|
15945
|
+
flex-shrink: 0;
|
|
15946
|
+
width: 1rem;
|
|
15947
|
+
height: 1rem;
|
|
15948
|
+
|
|
15949
|
+
&:after {
|
|
15950
|
+
font-size: 1rem;
|
|
15951
|
+
color: ${({ theme }) => theme.palette.primary};
|
|
15952
|
+
}
|
|
15758
15953
|
`;
|
|
15759
15954
|
/**
|
|
15760
15955
|
* Ячейка цвета: плашка или палитра слева, значение справа.
|
|
@@ -15762,7 +15957,7 @@ const AttachmentsCellBox = styled.div.withConfig({ displayName: "AttachmentsCell
|
|
|
15762
15957
|
* Отступы те же, что у текстовой ячейки: в одном ряду с обычными колонками значения обязаны
|
|
15763
15958
|
* стоять на одной линии.
|
|
15764
15959
|
*/
|
|
15765
|
-
const ColorCellBox = styled.div.withConfig({ displayName: "ColorCellBox", componentId: "sc-
|
|
15960
|
+
const ColorCellBox = styled.div.withConfig({ displayName: "ColorCellBox", componentId: "sc-1x8c5gd" }) `
|
|
15766
15961
|
display: flex;
|
|
15767
15962
|
align-items: center;
|
|
15768
15963
|
gap: 0.375rem;
|
|
@@ -15777,38 +15972,119 @@ const ColorCellBox = styled.div.withConfig({ displayName: "ColorCellBox", compon
|
|
|
15777
15972
|
* Плашка цвета. Повторяет маркер легенды графика: цвет всюду обозначается одинаково, где бы он
|
|
15778
15973
|
* ни показывался.
|
|
15779
15974
|
*/
|
|
15780
|
-
const ColorSwatch = styled.div.withConfig({ displayName: "ColorSwatch", componentId: "sc-
|
|
15975
|
+
const ColorSwatch = styled.div.withConfig({ displayName: "ColorSwatch", componentId: "sc-1h7m0yr" }) `
|
|
15781
15976
|
flex-shrink: 0;
|
|
15782
15977
|
width: 0.75rem;
|
|
15783
15978
|
height: 0.75rem;
|
|
15784
15979
|
background-color: ${({ $color }) => $color};
|
|
15785
15980
|
border-radius: ${({ theme: { borderRadius } }) => borderRadius.tiny};
|
|
15786
15981
|
`;
|
|
15787
|
-
const TableEmpty = styled.div.withConfig({ displayName: "TableEmpty", componentId: "sc-
|
|
15982
|
+
const TableEmpty = styled.div.withConfig({ displayName: "TableEmpty", componentId: "sc-10hkdmv" }) `
|
|
15788
15983
|
padding: 0.75rem 0.25rem;
|
|
15789
15984
|
color: ${({ theme }) => theme.palette.textSecondary};
|
|
15790
15985
|
`;
|
|
15791
15986
|
|
|
15792
15987
|
/**
|
|
15793
|
-
*
|
|
15988
|
+
* Добавление и удаление файлов в колонке вложений.
|
|
15989
|
+
*
|
|
15990
|
+
* Все три источника — диск, каталог ресурсов и ссылка — те же, что у контейнера вложений
|
|
15991
|
+
* в карточке объекта: файловое api берётся из глобального контекста, диалог каталога выдаёт
|
|
15992
|
+
* виджету приложение, а ссылка разбирается по адресу.
|
|
15993
|
+
*
|
|
15994
|
+
* Папку-приёмник загрузки задаёт сам атрибут: у колонки нет узла-элемента с опциями, зато
|
|
15995
|
+
* колонок вложений в таблице может быть несколько, и складывать их файлы в одно место незачем.
|
|
15996
|
+
*/
|
|
15997
|
+
const useAttachmentsCellEdit = ({ attribute, items, persist }) => {
|
|
15998
|
+
const { api } = useGlobalContext();
|
|
15999
|
+
const { selectAttachmentsFromCatalog } = useWidgetContext();
|
|
16000
|
+
const [isLinkDialogOpen, , setLinkDialogOpen] = useToggle(false);
|
|
16001
|
+
const [uploading, setUploading] = React.useState(false);
|
|
16002
|
+
const { parentResourceId, fileExtensions } = attribute;
|
|
16003
|
+
const onDelete = React.useCallback((link) => persist(items.filter(item => item.link !== link)), [items, persist]);
|
|
16004
|
+
const onUpload = React.useCallback(async (files) => {
|
|
16005
|
+
if (!api?.file?.upload || uploading) {
|
|
16006
|
+
return;
|
|
16007
|
+
}
|
|
16008
|
+
setUploading(true);
|
|
16009
|
+
try {
|
|
16010
|
+
const uploaded = await Promise.all(files.map(file => api.file.upload(file, true, parentResourceId || "", file.name)));
|
|
16011
|
+
persist([
|
|
16012
|
+
...items,
|
|
16013
|
+
...uploaded.map((response, index) => ({
|
|
16014
|
+
link: response.resourceId,
|
|
16015
|
+
name: response.name ?? files[index].name,
|
|
16016
|
+
mimeType: files[index].type,
|
|
16017
|
+
date: new Date().toISOString(),
|
|
16018
|
+
isExternal: false,
|
|
16019
|
+
})),
|
|
16020
|
+
]);
|
|
16021
|
+
}
|
|
16022
|
+
finally {
|
|
16023
|
+
setUploading(false);
|
|
16024
|
+
}
|
|
16025
|
+
}, [api, items, parentResourceId, persist, uploading]);
|
|
16026
|
+
const onAddFromCatalog = React.useCallback((resources) => persist([
|
|
16027
|
+
...items,
|
|
16028
|
+
...resources.map(({ resourceId, name, contentType }) => ({
|
|
16029
|
+
link: resourceId ?? "",
|
|
16030
|
+
name: name ?? "",
|
|
16031
|
+
mimeType: contentType ?? "",
|
|
16032
|
+
date: new Date().toISOString(),
|
|
16033
|
+
isExternal: false,
|
|
16034
|
+
})),
|
|
16035
|
+
]), [items, persist]);
|
|
16036
|
+
const onSelectFromCatalog = React.useMemo(() => (selectAttachmentsFromCatalog ? () => selectAttachmentsFromCatalog(onAddFromCatalog) : undefined), [onAddFromCatalog, selectAttachmentsFromCatalog]);
|
|
16037
|
+
const onOpenLinkDialog = React.useCallback(() => setLinkDialogOpen(true), [setLinkDialogOpen]);
|
|
16038
|
+
const onCloseLinkDialog = React.useCallback(() => setLinkDialogOpen(false), [setLinkDialogOpen]);
|
|
16039
|
+
const onAddByLink = React.useCallback((url) => persist([
|
|
16040
|
+
...items,
|
|
16041
|
+
{
|
|
16042
|
+
link: url,
|
|
16043
|
+
name: getFileNameFromUrl(url),
|
|
16044
|
+
mimeType: getMimeTypeFromUrl(url),
|
|
16045
|
+
date: new Date().toISOString(),
|
|
16046
|
+
isExternal: true,
|
|
16047
|
+
},
|
|
16048
|
+
]), [items, persist]);
|
|
16049
|
+
return {
|
|
16050
|
+
accept: fileExtensions,
|
|
16051
|
+
isLinkDialogOpen,
|
|
16052
|
+
onDelete,
|
|
16053
|
+
onUpload,
|
|
16054
|
+
onSelectFromCatalog,
|
|
16055
|
+
onOpenLinkDialog,
|
|
16056
|
+
onCloseLinkDialog,
|
|
16057
|
+
onAddByLink,
|
|
16058
|
+
};
|
|
16059
|
+
};
|
|
16060
|
+
|
|
16061
|
+
/**
|
|
16062
|
+
* Колонка вложений: разбор значения, свёрнутый вид ячейки и полный список в поп-апе.
|
|
15794
16063
|
*
|
|
15795
16064
|
* Значение атрибута — строка: файлы приезжают в ней списком JSON, поэтому и обратно в черновик
|
|
15796
16065
|
* кладём строку, а не массив — иначе тип ячейки менялся бы от самой правки.
|
|
15797
16066
|
*
|
|
15798
|
-
*
|
|
15799
|
-
*
|
|
16067
|
+
* Ячейка показывает один файл, а несколько схлопывает в счётчик, и весь список с добавлением
|
|
16068
|
+
* и удалением живёт в поп-апе — тот же набор, что у контейнера вложений в карточке объекта.
|
|
15800
16069
|
*/
|
|
15801
|
-
const useAttachmentsCell = ({ attribute, row, canEdit, onChange }) => {
|
|
16070
|
+
const useAttachmentsCell = ({ attribute, row, canEdit, onChange, }) => {
|
|
15802
16071
|
const [previewIndex, setPreviewIndex] = React.useState(null);
|
|
15803
|
-
const [
|
|
16072
|
+
const [isPopupOpen, , setPopupOpen] = useToggle(false);
|
|
15804
16073
|
const { attributeName, isEditable } = attribute;
|
|
15805
16074
|
const value = row.properties[attributeName];
|
|
15806
16075
|
const items = React.useMemo(() => parseAttachments(value), [value]);
|
|
16076
|
+
const view = useAttachmentsView({
|
|
16077
|
+
items,
|
|
16078
|
+
limit: ATTACHMENTS_SHOWN_ITEMS,
|
|
16079
|
+
initialViewMode: ATTACHMENTS_VIEW_MODE,
|
|
16080
|
+
});
|
|
16081
|
+
const { setShowMore } = view;
|
|
15807
16082
|
const previewImages = useAttachmentPreviewImages({ items, active: previewIndex !== null });
|
|
15808
16083
|
const downloadByIndex = useAttachmentDownload(items);
|
|
15809
16084
|
// Те же два условия, что и у обычной ячейки: режим таблицы и разрешение самого атрибута.
|
|
15810
16085
|
const editable = canEdit && isEditable;
|
|
15811
16086
|
const persist = React.useCallback((next) => onChange(row.key, attributeName, JSON.stringify(next)), [attributeName, onChange, row.key]);
|
|
16087
|
+
const edit = useAttachmentsCellEdit({ attribute, items, persist });
|
|
15812
16088
|
const onPreview = React.useCallback((link) => {
|
|
15813
16089
|
const index = items.findIndex(item => item.link === link);
|
|
15814
16090
|
if (index >= 0) {
|
|
@@ -15817,48 +16093,78 @@ const useAttachmentsCell = ({ attribute, row, canEdit, onChange }) => {
|
|
|
15817
16093
|
}, [items]);
|
|
15818
16094
|
const onClosePreview = React.useCallback(() => setPreviewIndex(null), []);
|
|
15819
16095
|
const onDownload = React.useCallback((_image, index) => downloadByIndex(index), [downloadByIndex]);
|
|
15820
|
-
const
|
|
15821
|
-
const
|
|
15822
|
-
|
|
15823
|
-
|
|
15824
|
-
|
|
15825
|
-
|
|
15826
|
-
|
|
15827
|
-
|
|
15828
|
-
|
|
15829
|
-
|
|
15830
|
-
|
|
15831
|
-
}
|
|
15832
|
-
|
|
16096
|
+
const onShowMore = React.useCallback(() => setShowMore(true), [setShowMore]);
|
|
16097
|
+
const onClosePopup = React.useCallback(() => setPopupOpen(false), [setPopupOpen]);
|
|
16098
|
+
/**
|
|
16099
|
+
* На правку поп-ап открывается всегда, даже у пустой ячейки: другого места, откуда добавить
|
|
16100
|
+
* первый файл, у колонки нет. На чтение единственный файл ведёт прямо в галерею, а свёрнутый
|
|
16101
|
+
* счётчик — в тот же поп-ап, только без кнопок.
|
|
16102
|
+
*/
|
|
16103
|
+
const onCellClick = React.useCallback(() => {
|
|
16104
|
+
if (!editable && items.length === ATTACHMENTS_INLINE_LIMIT) {
|
|
16105
|
+
onPreview(items[0].link);
|
|
16106
|
+
return;
|
|
16107
|
+
}
|
|
16108
|
+
if (editable || items.length) {
|
|
16109
|
+
setPopupOpen(true);
|
|
16110
|
+
}
|
|
16111
|
+
}, [editable, items, onPreview, setPopupOpen]);
|
|
15833
16112
|
return {
|
|
16113
|
+
...view,
|
|
16114
|
+
...edit,
|
|
15834
16115
|
items,
|
|
15835
16116
|
editable,
|
|
16117
|
+
onShowMore,
|
|
16118
|
+
isPopupOpen,
|
|
16119
|
+
onClosePopup,
|
|
16120
|
+
onCellClick,
|
|
15836
16121
|
previewIndex,
|
|
15837
16122
|
previewImages,
|
|
15838
|
-
isLinkDialogOpen,
|
|
15839
16123
|
onPreview,
|
|
15840
16124
|
onClosePreview,
|
|
15841
16125
|
onDownload,
|
|
15842
|
-
onDelete,
|
|
15843
|
-
onOpenLinkDialog,
|
|
15844
|
-
onCloseLinkDialog,
|
|
15845
|
-
onAddByLink,
|
|
15846
16126
|
};
|
|
15847
16127
|
};
|
|
15848
16128
|
|
|
15849
16129
|
/**
|
|
15850
|
-
* Колонка со
|
|
16130
|
+
* Колонка со вложениями.
|
|
15851
16131
|
*
|
|
15852
16132
|
* Тип у атрибута строковый, вложениями его делает `subType: "Attachments"` в схеме контейнера —
|
|
15853
16133
|
* поэтому ветка стоит раньше разбора типа и обычных редакторов ячейки.
|
|
16134
|
+
*
|
|
16135
|
+
* Сама ячейка показывает один файл, а несколько схлопывает в строку со значком ссылки
|
|
16136
|
+
* и счётчиком: в ширину колонки список файлов не помещается. Весь список живёт в поп-апе.
|
|
15854
16137
|
*/
|
|
15855
16138
|
const AttachmentsCell = React.memo(({ attribute, row, canEdit, onChange }) => {
|
|
15856
16139
|
const { t } = useGlobalContext();
|
|
15857
|
-
const
|
|
15858
|
-
|
|
16140
|
+
const state = useAttachmentsCell({ attribute, row, canEdit, onChange });
|
|
16141
|
+
const { items, editable, isPopupOpen, onClosePopup, onCellClick } = state;
|
|
16142
|
+
const [single] = items;
|
|
16143
|
+
const isCollapsed = items.length > ATTACHMENTS_INLINE_LIMIT;
|
|
16144
|
+
const fileType = React.useMemo(() => (single ? getFileType(single.mimeType, single.name) : undefined), [single]);
|
|
16145
|
+
const isImage = React.useMemo(() => !!fileType && IMAGE_FILE_TYPES.includes(fileType), [fileType]);
|
|
16146
|
+
// Удаление единственного файла — действие самой ячейки, поп-ап ради него открывать незачем.
|
|
16147
|
+
const handleDelete = React.useCallback((event) => {
|
|
16148
|
+
event.stopPropagation();
|
|
16149
|
+
state.onDelete(single.link);
|
|
16150
|
+
}, [single, state]);
|
|
16151
|
+
const renderCell = () => {
|
|
16152
|
+
if (isCollapsed) {
|
|
16153
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(AttachmentsCellLinkIcon, { kind: "link" }), jsxRuntime.jsx(AttachmentsCellLabel, { children: t("attachments.objectsCount", {
|
|
16154
|
+
ns: "common",
|
|
16155
|
+
total: items.length,
|
|
16156
|
+
defaultValue: "Объектов: {{total}}",
|
|
16157
|
+
}) })] }));
|
|
16158
|
+
}
|
|
16159
|
+
if (!single) {
|
|
16160
|
+
return jsxRuntime.jsx(CellPlaceholder, { children: "\u2014" });
|
|
16161
|
+
}
|
|
16162
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [isImage ? (jsxRuntime.jsx(FileImagePreview, { size: "1rem", isExternal: single.isExternal, link: single.link })) : (jsxRuntime.jsx(AttachmentsCellIcon, { fileType: fileType })), jsxRuntime.jsx(AttachmentsCellLabel, { title: single.name, children: single.name }), editable && jsxRuntime.jsx(uilibGl.IconButton, { kind: "delete", tabIndex: 0, onClick: handleDelete })] }));
|
|
16163
|
+
};
|
|
16164
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(uilibGl.Popup, { zIndex: DASHBOARD_OVERLAY_Z_INDEX, isVisible: isPopupOpen, onRequestClose: onClosePopup, placement: "bottom-start", anchor: ref => (jsxRuntime.jsx(AttachmentsCellBox, { ref: ref, "$interactive": editable || !!items.length, onClick: onCellClick, children: renderCell() })), children: jsxRuntime.jsx(AttachmentsCellPopup, { alias: attribute.alias, state: state }) }), jsxRuntime.jsx(AttachmentLinkDialog, { isOpen: state.isLinkDialogOpen, onClose: state.onCloseLinkDialog, onSubmit: state.onAddByLink }), state.previewIndex !== null && (jsxRuntime.jsx(uilibGl.Preview, { images: state.previewImages, initialIndex: state.previewIndex, isOpen: true, onClose: state.onClosePreview, onDownload: state.onDownload, errorTitleText: t("attachments.resourceUnavailable", {
|
|
15859
16165
|
ns: "common",
|
|
15860
16166
|
defaultValue: "Ресурс недоступен",
|
|
15861
|
-
}) }, previewIndex))] }));
|
|
16167
|
+
}) }, state.previewIndex))] }));
|
|
15862
16168
|
});
|
|
15863
16169
|
|
|
15864
16170
|
/**
|
|
@@ -16768,13 +17074,43 @@ const getRenderElement = ({ t, config, elementConfig, attributes = [], layerInfo
|
|
|
16768
17074
|
});
|
|
16769
17075
|
};
|
|
16770
17076
|
|
|
17077
|
+
/**
|
|
17078
|
+
* Видимость диалога модалки. Флаг локальный: у одного `modalId` бывает несколько кнопок (слот
|
|
17079
|
+
* `modal` в строках `DataSource`), и общий флаг открыл бы все диалоги сразу. Хост узнаёт об
|
|
17080
|
+
* открытии и закрытии через `onModalToggle` — чтобы грузить `dataSources` модалки только пока она открыта.
|
|
17081
|
+
*/
|
|
17082
|
+
const useModalOpen = (type, modalId) => {
|
|
17083
|
+
const { onModalToggle } = useWidgetContext(type);
|
|
17084
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
17085
|
+
const isOpenRef = React.useRef(false);
|
|
17086
|
+
const onModalToggleRef = React.useRef(onModalToggle);
|
|
17087
|
+
React.useEffect(() => {
|
|
17088
|
+
onModalToggleRef.current = onModalToggle;
|
|
17089
|
+
}, [onModalToggle]);
|
|
17090
|
+
const changeOpen = React.useCallback((nextIsOpen) => {
|
|
17091
|
+
isOpenRef.current = nextIsOpen;
|
|
17092
|
+
setIsOpen(nextIsOpen);
|
|
17093
|
+
if (modalId)
|
|
17094
|
+
onModalToggleRef.current?.(modalId, nextIsOpen);
|
|
17095
|
+
}, [modalId]);
|
|
17096
|
+
const handleOpen = React.useCallback(() => changeOpen(true), [changeOpen]);
|
|
17097
|
+
const handleClose = React.useCallback(() => changeOpen(false), [changeOpen]);
|
|
17098
|
+
// Размонтирование ОТКРЫТОГО экземпляра (смена страницы, закрытие карточки) — хост перестаёт
|
|
17099
|
+
// грузить модалку. Закрытый экземпляр молчит: иначе «закрыл» бы соседний открытый с тем же id.
|
|
17100
|
+
React.useEffect(() => () => {
|
|
17101
|
+
if (isOpenRef.current && modalId)
|
|
17102
|
+
onModalToggleRef.current?.(modalId, false);
|
|
17103
|
+
}, [modalId]);
|
|
17104
|
+
return { isOpen, handleOpen, handleClose };
|
|
17105
|
+
};
|
|
17106
|
+
|
|
16771
17107
|
const ElementModal = React.memo(({ type = exports.WidgetType.Dashboard, elementConfig }) => {
|
|
16772
17108
|
const { config } = useWidgetConfig(type);
|
|
16773
17109
|
const { expandedContainers, attributes } = useWidgetContext(type);
|
|
16774
17110
|
const isDataSourceLoading = useDataSourceLoading(type);
|
|
16775
|
-
const [isOpen, setIsOpen] = React.useState(false);
|
|
16776
17111
|
const { options } = elementConfig || {};
|
|
16777
17112
|
const { modalId, icon } = options || {};
|
|
17113
|
+
const { isOpen, handleOpen, handleClose } = useModalOpen(type, modalId);
|
|
16778
17114
|
const modalConfig = React.useMemo(() => (config?.modals ?? []).find(({ id }) => id === modalId) ?? null, [config?.modals, modalId]);
|
|
16779
17115
|
const modalContent = React.useMemo(() => (modalConfig?.children ?? []), [modalConfig]);
|
|
16780
17116
|
const renderElementConfig = React.useMemo(() => ({ children: modalContent }), [modalContent]);
|
|
@@ -16785,8 +17121,6 @@ const ElementModal = React.memo(({ type = exports.WidgetType.Dashboard, elementC
|
|
|
16785
17121
|
attributes,
|
|
16786
17122
|
expandedContainers,
|
|
16787
17123
|
}), [type, config, renderElementConfig, attributes, expandedContainers]);
|
|
16788
|
-
const handleOpen = React.useCallback(() => setIsOpen(true), []);
|
|
16789
|
-
const handleClose = React.useCallback(() => setIsOpen(false), []);
|
|
16790
17124
|
if (!modalConfig)
|
|
16791
17125
|
return null;
|
|
16792
17126
|
const { options: modalOptions } = modalConfig;
|
|
@@ -16951,6 +17285,7 @@ const RangeNumberFilter = ({ type, filter }) => {
|
|
|
16951
17285
|
const TextFilter = ({ type, filter }) => {
|
|
16952
17286
|
const { filters, changeFilters, dataSources } = useWidgetContext(type);
|
|
16953
17287
|
const { currentPage } = useWidgetPage(type);
|
|
17288
|
+
const configDataSources = useConfigDataSources(type);
|
|
16954
17289
|
const suggestRef = React.useRef(null);
|
|
16955
17290
|
const [totalCount, setTotalCount] = React.useState(0);
|
|
16956
17291
|
const { attributes, layerInfo } = useRelatedDataSourceAttributes({
|
|
@@ -16958,7 +17293,7 @@ const TextFilter = ({ type, filter }) => {
|
|
|
16958
17293
|
elementConfig: filter,
|
|
16959
17294
|
dataSources,
|
|
16960
17295
|
});
|
|
16961
|
-
const { filters: configFilters
|
|
17296
|
+
const { filters: configFilters } = currentPage || {};
|
|
16962
17297
|
const { filterName, searchFilterName, placeholder, width, height, multiSelect, variants, } = filter.options;
|
|
16963
17298
|
const { eqlParameters } = (layerInfo?.configuration ||
|
|
16964
17299
|
{});
|
|
@@ -17850,10 +18185,11 @@ const assembleTree = (nodes) => {
|
|
|
17850
18185
|
const useTreeFilterData = (type, filterName) => {
|
|
17851
18186
|
const { api } = useGlobalContext();
|
|
17852
18187
|
const { currentPage } = useWidgetPage(type);
|
|
18188
|
+
const configDataSources = useConfigDataSources(type);
|
|
17853
18189
|
const configFilter = React.useMemo(() => getConfigFilter(filterName, currentPage?.filters), [filterName, currentPage?.filters]);
|
|
17854
18190
|
const { relatedDataSource, attributeName = DEFAULT_ATTRIBUTE_NAME, attributeValue, attributeAlias = DEFAULT_ATTRIBUTE_NAME, attributeParentName, attributeLevel, attributeHasChildren, limit = DEFAULT_DATA_SOURCE_LIMIT, } = configFilter ?? {};
|
|
17855
18191
|
const valueAttribute = attributeValue ?? attributeName;
|
|
17856
|
-
const dataSource = React.useMemo(() =>
|
|
18192
|
+
const dataSource = React.useMemo(() => configDataSources.find(({ name }) => name === relatedDataSource), [configDataSources, relatedDataSource]);
|
|
17857
18193
|
const { layerName, query: sourceQuery, ds } = dataSource ?? {};
|
|
17858
18194
|
const mapNode = React.useCallback(({ properties }) => {
|
|
17859
18195
|
const hasChildren = attributeHasChildren ? !!properties[attributeHasChildren] : false;
|
|
@@ -19962,6 +20298,7 @@ exports.getLayerInfoAttribute = getLayerInfoAttribute;
|
|
|
19962
20298
|
exports.getLayerInfoFromDataSources = getLayerInfoFromDataSources;
|
|
19963
20299
|
exports.getLayoutChildren = getLayoutChildren;
|
|
19964
20300
|
exports.getMapViewDataSources = getMapViewDataSources;
|
|
20301
|
+
exports.getModalsDataSources = getModalsDataSources;
|
|
19965
20302
|
exports.getPagesFromConfig = getPagesFromConfig;
|
|
19966
20303
|
exports.getPagesFromProjectInfo = getPagesFromProjectInfo;
|
|
19967
20304
|
exports.getProjectValue = getProjectValue;
|
|
@@ -19971,6 +20308,7 @@ exports.getRenderElement = getRenderElement;
|
|
|
19971
20308
|
exports.getResourceUrl = getResourceUrl;
|
|
19972
20309
|
exports.getRootElementId = getRootElementId;
|
|
19973
20310
|
exports.getSelectedFilterValue = getSelectedFilterValue;
|
|
20311
|
+
exports.getShownItemsLimit = getShownItemsLimit;
|
|
19974
20312
|
exports.getSlideshowImages = getSlideshowImages;
|
|
19975
20313
|
exports.getStyleAttributes = getStyleAttributes;
|
|
19976
20314
|
exports.getSvgUrl = getSvgUrl;
|
|
@@ -20038,11 +20376,13 @@ exports.useAppHeight = useAppHeight;
|
|
|
20038
20376
|
exports.useAttachmentDownload = useAttachmentDownload;
|
|
20039
20377
|
exports.useAttachmentItems = useAttachmentItems;
|
|
20040
20378
|
exports.useAttachmentPreviewImages = useAttachmentPreviewImages;
|
|
20379
|
+
exports.useAttachmentsView = useAttachmentsView;
|
|
20041
20380
|
exports.useAutoCompleteControl = useAutoCompleteControl;
|
|
20042
20381
|
exports.useBeforeSave = useBeforeSave;
|
|
20043
20382
|
exports.useBgImageHost = useBgImageHost;
|
|
20044
20383
|
exports.useChartChange = useChartChange;
|
|
20045
20384
|
exports.useChartData = useChartData;
|
|
20385
|
+
exports.useConfigDataSources = useConfigDataSources;
|
|
20046
20386
|
exports.useContainerAttributes = useContainerAttributes;
|
|
20047
20387
|
exports.useContainerRoot = useContainerRoot;
|
|
20048
20388
|
exports.useCurrentPageLayers = useCurrentPageLayers;
|