@evergis/react 4.0.54 → 4.0.55

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.
@@ -0,0 +1,8 @@
1
+ import { IPreviewImage } from '@evergis/uilib-gl';
2
+ import { Attachment } from './types';
3
+ interface UsePreviewImagesParams {
4
+ items: Attachment[];
5
+ active: boolean;
6
+ }
7
+ export declare const usePreviewImages: ({ items, active }: UsePreviewImagesParams) => IPreviewImage[];
8
+ export {};
@@ -1,7 +1,7 @@
1
1
  import { ConfigContainerChild, EditAttributeValue, WidgetType } from '../types';
2
2
  export declare const useEditControl: (type: WidgetType, elementConfig: ConfigContainerChild) => {
3
3
  control: import('../types').ConfigControl;
4
- value: Date | import('../../..').FeaturedDcExtendedGeometry | import('../../..').JSONAttributeValue[] | EditAttributeValue;
4
+ value: Date | import('../../..').FeaturedDcExtendedGeometry | EditAttributeValue;
5
5
  dataSource: import('../types').WidgetDataSource;
6
6
  items: any[];
7
7
  onChange: (newValue: EditAttributeValue) => void;
@@ -44,7 +44,7 @@ export interface BaseMapSettings {
44
44
  opacity?: number;
45
45
  showBuildings?: boolean;
46
46
  }
47
- export type EditAttributeValue = string | number | boolean;
47
+ export type EditAttributeValue = string | number | boolean | unknown[] | Record<string, unknown>;
48
48
  export interface CustomFeatureSelect {
49
49
  point?: {
50
50
  paint?: CircleLayerSpecification["paint"];
package/dist/index.js CHANGED
@@ -8042,10 +8042,31 @@ const useGlobalContext = () => {
8042
8042
  }), [language, translate, api, ewktGeometry, themeName]);
8043
8043
  };
8044
8044
 
8045
+ const ImagePreviewError = styled.div `
8046
+ display: flex;
8047
+ align-items: center;
8048
+ justify-content: center;
8049
+ width: 100%;
8050
+ height: 100%;
8051
+ background-color: ${({ theme }) => theme.palette.elementDark};
8052
+ border-radius: ${({ theme: { borderRadius: themeBorder }, borderRadius }) => borderRadius || themeBorder.smallest};
8053
+
8054
+ ${uilibGl.Icon} {
8055
+ width: 37.5%;
8056
+ height: 37.5%;
8057
+ }
8058
+
8059
+ ${uilibGl.Icon}:after {
8060
+ font-size: 1.5rem;
8061
+ color: ${({ theme }) => theme.palette.textSecondary};
8062
+ }
8063
+ `;
8045
8064
  const FileImagePreview = ({ link, isExternal, size, borderRadius, }) => {
8046
8065
  const { api } = useGlobalContext();
8047
8066
  const [imageSrc, setImageSrc] = React.useState();
8067
+ const [hasError, setHasError] = React.useState(false);
8048
8068
  React.useEffect(() => {
8069
+ setHasError(false);
8049
8070
  if (isExternal) {
8050
8071
  setImageSrc(link);
8051
8072
  return;
@@ -8063,14 +8084,18 @@ const FileImagePreview = ({ link, isExternal, size, borderRadius, }) => {
8063
8084
  objectUrl = URL.createObjectURL(blob);
8064
8085
  setImageSrc(objectUrl);
8065
8086
  })
8066
- .catch(() => { });
8087
+ .catch(() => {
8088
+ if (cancelled)
8089
+ return;
8090
+ setHasError(true);
8091
+ });
8067
8092
  return () => {
8068
8093
  cancelled = true;
8069
8094
  if (objectUrl)
8070
8095
  URL.revokeObjectURL(objectUrl);
8071
8096
  };
8072
8097
  }, [api, link, isExternal]);
8073
- return (jsxRuntime.jsxs(ImagePreviewContainer, { size: size, children: [!imageSrc && (jsxRuntime.jsx(ImagePreviewLoaderContainer, { children: jsxRuntime.jsx(uilibGl.LinearProgress, {}) })), imageSrc && (jsxRuntime.jsx(GridImagePreview, { borderRadius: borderRadius, size: size, src: imageSrc, alt: "" }))] }));
8098
+ return (jsxRuntime.jsxs(ImagePreviewContainer, { size: size, children: [hasError && (jsxRuntime.jsx(ImagePreviewError, { borderRadius: borderRadius, children: jsxRuntime.jsx(uilibGl.Icon, { kind: "alert" }) })), !hasError && !imageSrc && (jsxRuntime.jsx(ImagePreviewLoaderContainer, { children: jsxRuntime.jsx(uilibGl.LinearProgress, {}) })), !hasError && imageSrc && (jsxRuntime.jsx(GridImagePreview, { borderRadius: borderRadius, size: size, src: imageSrc, alt: "", onError: () => setHasError(true) }))] }));
8074
8099
  };
8075
8100
 
8076
8101
  const getFileType = (mimeType = "", name = "") => {
@@ -8223,6 +8248,60 @@ const useAttachmentContainer = ({ type, elementConfig, valueOverride, }) => {
8223
8248
  };
8224
8249
  };
8225
8250
 
8251
+ const usePreviewImages = ({ items, active }) => {
8252
+ const { api } = useGlobalContext();
8253
+ const [blobUrls, setBlobUrls] = React.useState({});
8254
+ const [failedLinks, setFailedLinks] = React.useState({});
8255
+ const inFlightRef = React.useRef(new Set());
8256
+ const blobUrlsRef = React.useRef(blobUrls);
8257
+ blobUrlsRef.current = blobUrls;
8258
+ React.useEffect(() => {
8259
+ if (!active || !api?.catalog?.getFile)
8260
+ return;
8261
+ items.forEach(item => {
8262
+ const fileType = getFileType(item.mimeType, item.name);
8263
+ const isImage = IMAGE_FILE_TYPES.includes(fileType);
8264
+ if (!isImage || item.isExternal)
8265
+ return;
8266
+ if (blobUrlsRef.current[item.link] || inFlightRef.current.has(item.link))
8267
+ return;
8268
+ inFlightRef.current.add(item.link);
8269
+ api.catalog
8270
+ .getFile(item.link)
8271
+ .then(blob => {
8272
+ const objectUrl = URL.createObjectURL(blob);
8273
+ setBlobUrls(prev => ({ ...prev, [item.link]: objectUrl }));
8274
+ })
8275
+ .catch(() => {
8276
+ setFailedLinks(prev => (prev[item.link] ? prev : { ...prev, [item.link]: true }));
8277
+ })
8278
+ .finally(() => {
8279
+ inFlightRef.current.delete(item.link);
8280
+ });
8281
+ });
8282
+ }, [active, api, items]);
8283
+ React.useEffect(() => () => {
8284
+ Object.values(blobUrlsRef.current).forEach(URL.revokeObjectURL);
8285
+ }, []);
8286
+ return React.useMemo(() => items.map(item => {
8287
+ const fileType = getFileType(item.mimeType, item.name);
8288
+ const isImage = IMAGE_FILE_TYPES.includes(fileType);
8289
+ const fileName = item.name;
8290
+ if (!isImage)
8291
+ return { src: getFileTypeIcon(fileType), fileName };
8292
+ if (item.isExternal)
8293
+ return { src: item.link, fileName };
8294
+ const hasError = !!failedLinks[item.link];
8295
+ const blobUrl = blobUrls[item.link];
8296
+ return {
8297
+ src: blobUrl ?? "",
8298
+ fileName,
8299
+ hasError,
8300
+ isLoading: !hasError && !blobUrl,
8301
+ };
8302
+ }), [items, blobUrls, failedLinks]);
8303
+ };
8304
+
8226
8305
  const EXTENSION_TO_MIME = {
8227
8306
  apng: "image/apng",
8228
8307
  avif: "image/avif",
@@ -8269,20 +8348,6 @@ const getFileNameFromUrl = (url) => {
8269
8348
  }
8270
8349
  };
8271
8350
 
8272
- const getResourceUrl = (url) => {
8273
- return url ? (url.startsWith("http") ? url : `/sp/resources/file/${url}`) : "";
8274
- };
8275
-
8276
- const buildPreviewImage$1 = (item) => {
8277
- const fileType = getFileType(item.mimeType, item.name);
8278
- const isImage = IMAGE_FILE_TYPES.includes(fileType);
8279
- const src = isImage
8280
- ? item.isExternal
8281
- ? item.link
8282
- : getResourceUrl(item.link)
8283
- : getFileTypeIcon(fileType);
8284
- return { src, fileName: item.name };
8285
- };
8286
8351
  const EditAttachmentContainer = React.memo(({ type, elementConfig, renderElement }) => {
8287
8352
  const { api } = useGlobalContext();
8288
8353
  const { selectAttachmentsFromCatalog } = useWidgetContext(type);
@@ -8293,7 +8358,7 @@ const EditAttachmentContainer = React.memo(({ type, elementConfig, renderElement
8293
8358
  const [previewIndex, setPreviewIndex] = React.useState(null);
8294
8359
  const [uploading, setUploading] = React.useState(false);
8295
8360
  const [isLinkDialogOpen, , setLinkDialogOpen] = useToggle(false);
8296
- const previewImages = React.useMemo(() => items.map(buildPreviewImage$1), [items]);
8361
+ const previewImages = usePreviewImages({ items, active: previewIndex !== null });
8297
8362
  const orderedPreviewImages = React.useMemo(() => {
8298
8363
  if (previewIndex === null)
8299
8364
  return previewImages;
@@ -8302,7 +8367,7 @@ const EditAttachmentContainer = React.memo(({ type, elementConfig, renderElement
8302
8367
  ...previewImages.filter((_, idx) => idx !== previewIndex),
8303
8368
  ];
8304
8369
  }, [previewImages, previewIndex]);
8305
- const persist = React.useCallback((next) => onChange(JSON.stringify(next)), [onChange]);
8370
+ const persist = React.useCallback((next) => onChange(next), [onChange]);
8306
8371
  const handlePreview = React.useCallback((link) => {
8307
8372
  const idx = items.findIndex(item => item.link === link);
8308
8373
  if (idx >= 0)
@@ -8362,23 +8427,14 @@ const EditAttachmentContainer = React.memo(({ type, elementConfig, renderElement
8362
8427
  return (jsxRuntime.jsxs(AttachmentsContainer, { id: id, style: { ...BASE_CONTAINER_STYLE, ...style }, children: [jsxRuntime.jsx(AttachmentsHeader, { alias: renderElement?.({ id: "alias" }), count: items.length, viewMode: viewMode, onChangeViewMode: setViewMode }), jsxRuntime.jsx(AttachmentsContent, { children: viewMode === "grid" ? (jsxRuntime.jsx(AttachmentsGrid, { items: visibleItems, isEdit: true, onPreview: handlePreview, onDelete: handleDelete })) : (jsxRuntime.jsx(AttachmentsList, { items: visibleItems, isEdit: true, onPreview: handlePreview, onDelete: handleDelete })) }), hasMore && !showMore && (jsxRuntime.jsx(ShowMoreButton, { hiddenCount: hiddenCount, onClick: handleShowMore })), jsxRuntime.jsx(AddButton, { accept: fileExtensions, onSelectFiles: uploading ? () => undefined : handleUpload, onSelectFromCatalog: handleSelectFromCatalog, onSelectFromLink: handleOpenLinkDialog }), jsxRuntime.jsx(AttachmentLinkDialog, { isOpen: isLinkDialogOpen, onClose: handleCloseLinkDialog, onSubmit: handleAddByLink }), previewIndex !== null && (jsxRuntime.jsx(uilibGl.Preview, { images: orderedPreviewImages, isOpen: previewIndex !== null, onClose: handleClosePreview }))] }));
8363
8428
  });
8364
8429
 
8365
- const buildPreviewImage = (item) => {
8366
- const fileType = getFileType(item.mimeType, item.name);
8367
- const isImage = IMAGE_FILE_TYPES.includes(fileType);
8368
- const src = isImage
8369
- ? item.isExternal
8370
- ? item.link
8371
- : getResourceUrl(item.link)
8372
- : getFileTypeIcon(fileType);
8373
- return { src, fileName: item.name };
8374
- };
8375
8430
  const AttachmentContainer = React.memo(({ type, elementConfig, renderElement }) => {
8431
+ const { t } = useGlobalContext();
8376
8432
  const { expandedContainers } = useWidgetContext(type);
8377
8433
  const { items, visibleItems, viewMode, setViewMode, showMore, setShowMore, hasMore, hiddenCount } = useAttachmentContainer({ type, elementConfig });
8378
8434
  const { id, style, options } = elementConfig || {};
8379
8435
  const { expandable, expanded } = options || {};
8380
8436
  const [previewIndex, setPreviewIndex] = React.useState(null);
8381
- const previewImages = React.useMemo(() => items.map(buildPreviewImage), [items]);
8437
+ const previewImages = usePreviewImages({ items, active: previewIndex !== null });
8382
8438
  const orderedPreviewImages = React.useMemo(() => {
8383
8439
  if (previewIndex === null)
8384
8440
  return previewImages;
@@ -8395,7 +8451,10 @@ const AttachmentContainer = React.memo(({ type, elementConfig, renderElement })
8395
8451
  const handleClosePreview = React.useCallback(() => setPreviewIndex(null), []);
8396
8452
  const handleShowMore = React.useCallback(() => setShowMore(true), [setShowMore]);
8397
8453
  const isVisible = isVisibleContainer(id, expandable, expanded, expandedContainers);
8398
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsxRuntime.jsxs(Container, { id: id, isColumn: true, style: style, children: [jsxRuntime.jsx(AttachmentsHeader, { alias: renderElement?.({ id: "alias" }), count: items.length, viewMode: viewMode, onChangeViewMode: setViewMode }), jsxRuntime.jsx(AttachmentsContent, { children: viewMode === "grid" ? (jsxRuntime.jsx(AttachmentsGrid, { items: visibleItems, isEdit: false, onPreview: handlePreview })) : (jsxRuntime.jsx(AttachmentsList, { items: visibleItems, isEdit: false, onPreview: handlePreview })) }), hasMore && !showMore && (jsxRuntime.jsx(ShowMoreButton, { hiddenCount: hiddenCount, onClick: handleShowMore })), previewIndex !== null && (jsxRuntime.jsx(uilibGl.Preview, { images: orderedPreviewImages, isOpen: previewIndex !== null, onClose: handleClosePreview }))] }))] }));
8454
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsxRuntime.jsxs(Container, { id: id, isColumn: true, style: style, children: [jsxRuntime.jsx(AttachmentsHeader, { alias: renderElement?.({ id: "alias" }), count: items.length, viewMode: viewMode, onChangeViewMode: setViewMode }), jsxRuntime.jsx(AttachmentsContent, { children: viewMode === "grid" ? (jsxRuntime.jsx(AttachmentsGrid, { items: visibleItems, isEdit: false, onPreview: handlePreview })) : (jsxRuntime.jsx(AttachmentsList, { items: visibleItems, isEdit: false, onPreview: handlePreview })) }), hasMore && !showMore && (jsxRuntime.jsx(ShowMoreButton, { hiddenCount: hiddenCount, onClick: handleShowMore })), previewIndex !== null && (jsxRuntime.jsx(uilibGl.Preview, { images: orderedPreviewImages, isOpen: previewIndex !== null, onClose: handleClosePreview, errorTitleText: t("attachments.resourceUnavailable", {
8455
+ ns: "common",
8456
+ defaultValue: "Ресурс недоступен",
8457
+ }) }))] }))] }));
8399
8458
  });
8400
8459
 
8401
8460
  const ContainerDivider = styled(uilibGl.Divider) `
@@ -11174,6 +11233,10 @@ const getRelatedAttribute = (layerInfo, sourceAttributeName, relatedLayerName) =
11174
11233
  return attributeConfig?.options?.relatedAttributes?.find(({ layerName }) => layerName === relatedLayerName);
11175
11234
  };
11176
11235
 
11236
+ const getResourceUrl = (url) => {
11237
+ return url ? (url.startsWith("http") ? url : `/sp/resources/file/${url}`) : "";
11238
+ };
11239
+
11177
11240
  const getSlideshowImages = ({ element, attribute, }) => {
11178
11241
  const { defaultValue, options } = element;
11179
11242
  const { separator } = options || {};