@evergis/react 4.0.64 → 4.0.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5747,6 +5747,49 @@ const usePythonTask = () => {
5747
5747
  };
5748
5748
  };
5749
5749
 
5750
+ const useRemoteTask = ({ onUpdate } = {}) => {
5751
+ const { api: api$1 } = useGlobalContext();
5752
+ const { addSubscription, connection, unsubscribeById } = useServerNotificationsContext();
5753
+ const runTask = React.useCallback((prototype) => new Promise((resolve, reject) => {
5754
+ const start = async () => {
5755
+ const resourceId = prototype.subTaskSettings?.[0]?.startParameters?.resourceId;
5756
+ let prototypeId = await api$1.remoteTaskManager.createTaskPrototype(prototype);
5757
+ prototypeId = prototypeId.replace(/["']+/g, "");
5758
+ const { id: taskId, success } = await api$1.remoteTaskManager.startTask1(prototypeId);
5759
+ if (!success || !taskId) {
5760
+ reject(new Error("startTask returned no taskId"));
5761
+ return;
5762
+ }
5763
+ const subscriptionId = await addSubscription({
5764
+ tag: "python_task_progress_event",
5765
+ resourceId,
5766
+ });
5767
+ const onNotification = ({ data }) => {
5768
+ if (data?.taskId !== taskId) {
5769
+ return;
5770
+ }
5771
+ onUpdate?.({ status: data.status, log: data.log });
5772
+ const isCompleted = data.status === api.RemoteTaskStatus.Completed;
5773
+ const isError = data.status === api.RemoteTaskStatus.Error;
5774
+ if (isCompleted || isError) {
5775
+ connection?.off(SERVER_NOTIFICATION_EVENT.PythonProgressNotification, onNotification);
5776
+ if (subscriptionId) {
5777
+ unsubscribeById(subscriptionId);
5778
+ }
5779
+ resolve({
5780
+ taskId,
5781
+ status: isCompleted ? api.RemoteTaskStatus.Completed : api.RemoteTaskStatus.Error,
5782
+ log: data.log,
5783
+ });
5784
+ }
5785
+ };
5786
+ connection?.on(SERVER_NOTIFICATION_EVENT.PythonProgressNotification, onNotification);
5787
+ };
5788
+ start().catch(reject);
5789
+ }), [api$1, addSubscription, connection, unsubscribeById, onUpdate]);
5790
+ return { runTask };
5791
+ };
5792
+
5750
5793
  const useAppHeight = () => {
5751
5794
  React.useEffect(() => {
5752
5795
  const setAppHeight = () => {
@@ -6214,6 +6257,8 @@ const getAttributeByName = (name, attributes) => {
6214
6257
  : null;
6215
6258
  };
6216
6259
 
6260
+ const isEmptyElementValue = (value) => value === "" || value === null || value === undefined;
6261
+
6217
6262
  /**
6218
6263
  * Returns a value safe to render as a React child.
6219
6264
  *
@@ -6238,7 +6283,7 @@ const formatElementValue = ({ t, value, elementConfig, attributes, wrap, }) => {
6238
6283
  ? getAttributeByName(attributeName, attributes)
6239
6284
  : null;
6240
6285
  const { fontColor, fontSize, noUnits, tagView, bgColor, withDivider, radius, noMargin, } = options || {};
6241
- const valueOrDefault = value || defaultValue;
6286
+ const valueOrDefault = isEmptyElementValue(value) ? defaultValue : value;
6242
6287
  const resultValue = type === "attributeValue" && attribute?.type && attribute?.stringFormat
6243
6288
  ? formatAttributeValue({
6244
6289
  t,
@@ -6262,14 +6307,14 @@ const getAttributeValue = (element, attributes) => {
6262
6307
  return jsxRuntime.jsx(DashboardCheckbox, { title: attribute.alias || attribute.attributeName, checked: attribute.value });
6263
6308
  }
6264
6309
  if (Array.isArray(element?.attributeName)) {
6265
- const concatAttributes = element.attributeName.map((name) => attributes?.find(({ attributeName }) => attributeName === name)?.value || "");
6310
+ const concatAttributes = element.attributeName.map((name) => attributes?.find(({ attributeName }) => attributeName === name)?.value ?? "");
6266
6311
  value = concatAttributes.join(separator || ", ");
6267
6312
  }
6268
6313
  else {
6269
6314
  const rawValue = attribute?.value;
6270
- value = rawValue && typeof rawValue === "object"
6315
+ value = rawValue !== null && rawValue !== undefined && typeof rawValue === "object"
6271
6316
  ? JSON.stringify(rawValue)
6272
- : (rawValue || "");
6317
+ : (rawValue ?? "");
6273
6318
  }
6274
6319
  return typeof value === "string" && maxLength && maxLength < value.length ? (jsxRuntime.jsx(TextTrim, { maxLength: maxLength, wordBreak: wordBreak, expandable: expandable, lineBreak: lineBreak, children: value })) : (value);
6275
6320
  };
@@ -6478,7 +6523,7 @@ const useRenderContainer = ({ elementConfig, type, renderElement, renderBody, })
6478
6523
  return (jsxRuntime.jsx(OverrideContainer, { type: type, elementConfig: itemConfig, renderElement: item.render }, attribute));
6479
6524
  }
6480
6525
  }
6481
- if (!item.value && item.hideEmpty)
6526
+ if (isEmptyElementValue(item.value) && item.hideEmpty)
6482
6527
  return null;
6483
6528
  return renderBody(item, attribute);
6484
6529
  }, [getRenderContainerItem, elementConfig, attributes, type, renderBody]);
@@ -7647,7 +7692,7 @@ const LogDialog = ({ isOpen, onClose, onMinimize, logs, status, statusColors })
7647
7692
  const getStatusColor = React.useCallback((currentStatus) => {
7648
7693
  return statusColors?.[currentStatus] || STATUS_COLORS[currentStatus] || STATUS_COLORS[api.RemoteTaskStatus.Unknown];
7649
7694
  }, [statusColors]);
7650
- return (jsxRuntime.jsxs(uilibGl.Dialog, { isOpen: isOpen, onCloseRequest: onClose, modal: true, maxWidth: "800px", minWidth: "600px", minHeight: "600px", children: [jsxRuntime.jsx(uilibGl.DialogTitle, { children: jsxRuntime.jsxs(uilibGl.Flex, { justifyContent: "space-between", alignItems: "center", children: [jsxRuntime.jsxs(uilibGl.Flex, { alignItems: "center", children: [jsxRuntime.jsx(uilibGl.FlexSpan, { marginRight: "1rem", children: t("taskLogs", { ns: "dashboard", defaultValue: "Логи выполнения задачи" }) }), jsxRuntime.jsx(uilibGl.ThemeProvider, { theme: uilibGl.darkTheme, children: jsxRuntime.jsx(StatusBadge, { text: getStatusTitle(status), bgColor: getStatusColor(status) }) })] }), jsxRuntime.jsxs(uilibGl.Flex, { alignItems: "center", width: "auto", children: [onMinimize && jsxRuntime.jsx(uilibGl.IconButton, { kind: "change_geom", onClick: onMinimize }), jsxRuntime.jsx(uilibGl.IconButton, { kind: "close", onClick: onClose })] })] }) }), jsxRuntime.jsx(uilibGl.DialogContent, { children: jsxRuntime.jsx(uilibGl.Flex, { flexDirection: "column", height: "100%", marginBottom: "2rem", children: jsxRuntime.jsx(LogTerminal, { log: logs || t("taskLogsEmpty", { ns: "dashboard", defaultValue: "Логи отсутствуют" }) }) }) })] }));
7695
+ return (jsxRuntime.jsxs(uilibGl.Dialog, { isOpen: isOpen, onCloseRequest: onClose, modal: true, maxWidth: "800px", minWidth: "600px", minHeight: "600px", children: [jsxRuntime.jsx(uilibGl.DialogTitle, { children: jsxRuntime.jsxs(uilibGl.Flex, { justifyContent: "space-between", alignItems: "center", children: [jsxRuntime.jsxs(uilibGl.Flex, { alignItems: "center", children: [jsxRuntime.jsx(uilibGl.FlexSpan, { marginRight: "1rem", children: t("taskLogs", { ns: "dashboard", defaultValue: "Логи выполнения задачи" }) }), jsxRuntime.jsx(uilibGl.ThemeProvider, { theme: uilibGl.darkTheme, children: jsxRuntime.jsx(StatusBadge, { text: getStatusTitle(status), bgColor: getStatusColor(status) }) })] }), jsxRuntime.jsxs(uilibGl.Flex, { alignItems: "center", width: "auto", children: [onMinimize && jsxRuntime.jsx(uilibGl.IconButton, { kind: "rollup", onClick: onMinimize }), jsxRuntime.jsx(uilibGl.IconButton, { kind: "close", onClick: onClose })] })] }) }), jsxRuntime.jsx(uilibGl.DialogContent, { children: jsxRuntime.jsx(uilibGl.Flex, { flexDirection: "column", height: "100%", marginBottom: "2rem", children: jsxRuntime.jsx(LogTerminal, { log: logs || t("taskLogsEmpty", { ns: "dashboard", defaultValue: "Логи отсутствуют" }) }) }) })] }));
7651
7696
  };
7652
7697
 
7653
7698
  exports.ThemeName = void 0;
@@ -9038,115 +9083,6 @@ const FeatureCardDefaultHeader = ({ noFeature }) => {
9038
9083
  return (jsxRuntime.jsx(DefaultHeaderWrapper, { withPadding: withPadding, height: height, children: jsxRuntime.jsx(uilibGl.ThemeProvider, { theme: getThemeByName(themeName), children: jsxRuntime.jsx(Header, { "$overlay": overlay, "$isRow": !column, children: jsxRuntime.jsxs(HeaderFrontView, { isDefault: !column, children: [jsxRuntime.jsxs(HeaderContainer, { children: [jsxRuntime.jsx(HeaderLayerIcon, {}), jsxRuntime.jsx(HeaderTitle, { noFeature: noFeature })] }), jsxRuntime.jsx(FeatureCardButtons, {})] }) }) }) }));
9039
9084
  };
9040
9085
 
9041
- const HeaderWrapperMixin = styled.css `
9042
- ${Header} {
9043
- min-height: 5.25rem;
9044
- }
9045
-
9046
- ${HeaderContainer} {
9047
- max-width: 100%;
9048
- width: 100%;
9049
- }
9050
-
9051
- ${FeatureControls} {
9052
- max-width: calc(100% - 2rem);
9053
- width: calc(100% - 2rem);
9054
- margin-top: -0.5rem;
9055
- padding-top: 1rem;
9056
- border-radius: ${({ theme: { borderRadius } }) => borderRadius.medium};
9057
- }
9058
-
9059
- ${({ $fontColor }) => !!$fontColor && HeaderFontColorMixin};
9060
- `;
9061
- const HeaderIcon = styled(uilibGl.Flex) `
9062
- position: absolute;
9063
- top: 0;
9064
- right: 0;
9065
- align-items: center;
9066
- justify-content: center;
9067
- width: 7.625rem;
9068
- height: 100%;
9069
-
9070
- span[kind] {
9071
- width: 4rem;
9072
-
9073
- :after {
9074
- font-size: 4rem;
9075
- color: rgba(255, 255, 255, 0.36);
9076
- }
9077
- }
9078
-
9079
- span[kind]:after,
9080
- path,
9081
- line,
9082
- circle {
9083
- fill: rgba(255, 255, 255, 0.36);
9084
- }
9085
-
9086
- && > * {
9087
- display: flex;
9088
- align-items: center;
9089
- height: 100%;
9090
- }
9091
- `;
9092
- const BigIconHeaderMixin = styled.css `
9093
- ${HeaderIcon} {
9094
- min-width: 14rem;
9095
- right: -3rem;
9096
-
9097
- span[kind]:after {
9098
- font-size: 14rem;
9099
- }
9100
- }
9101
- `;
9102
- const WithPaddingHeaderMixin = styled.css `
9103
- ${Header} {
9104
- width: 100%;
9105
- margin: -0.5rem -0.5rem 0.5rem -0.5rem;
9106
- }
9107
- `;
9108
- const BottomBlurHeaderMixin = styled.css `
9109
- ${Header} {
9110
- margin-bottom: 0;
9111
-
9112
- &::after {
9113
- content: "";
9114
- position: absolute;
9115
- top: 0;
9116
- right: 0;
9117
- bottom: 0;
9118
- left: 0;
9119
- z-index: 11;
9120
- pointer-events: none;
9121
- background: ${({ theme: { palette } }) => palette.background};
9122
- mask-image: linear-gradient(to top, #000 0%, transparent 100%);
9123
- -webkit-mask-image: linear-gradient(to top, #000 0%, transparent 100%);
9124
- }
9125
- }
9126
-
9127
- ${HeaderFrontView} {
9128
- z-index: 12;
9129
- }
9130
- `;
9131
- const BackgroundHeaderWrapper = styled.div `
9132
- ${Header} {
9133
- width: calc(100% + 1rem);
9134
- height: ${({ $height }) => $height ? `${$height}px` : "auto"};
9135
- margin: -1rem -1rem 1rem -1rem;
9136
- border-radius: 0.5rem;
9137
- background: ${({ $bgColor }) => $bgColor || "linear-gradient(96.55deg, #FFFCD3 0%, #B4DC47 100%)"};
9138
- overflow: hidden;
9139
- }
9140
-
9141
- ${HeaderWrapperMixin};
9142
-
9143
- ${({ $bigIcon }) => $bigIcon && BigIconHeaderMixin};
9144
-
9145
- ${({ $withPadding }) => $withPadding && WithPaddingHeaderMixin};
9146
-
9147
- ${({ $bottomBlur }) => $bottomBlur && BottomBlurHeaderMixin};
9148
- `;
9149
-
9150
9086
  const ImageContainerButton = styled(uilibGl.FlatButton) `
9151
9087
  min-height: 1.5rem;
9152
9088
  border-radius: ${({ theme: { borderRadius } }) => borderRadius.large};
@@ -10065,6 +10001,121 @@ const HeaderSlideshow = styled.div `
10065
10001
  }
10066
10002
  `;
10067
10003
 
10004
+ const HeaderWrapperMixin = styled.css `
10005
+ ${Header} {
10006
+ min-height: 5.25rem;
10007
+ }
10008
+
10009
+ ${HeaderContainer} {
10010
+ max-width: 100%;
10011
+ width: 100%;
10012
+ }
10013
+
10014
+ ${FeatureControls} {
10015
+ max-width: calc(100% - 2rem);
10016
+ width: calc(100% - 2rem);
10017
+ margin-top: -0.5rem;
10018
+ padding-top: 1rem;
10019
+ border-radius: ${({ theme: { borderRadius } }) => borderRadius.medium};
10020
+ }
10021
+
10022
+ ${({ $fontColor }) => !!$fontColor && HeaderFontColorMixin};
10023
+ `;
10024
+ const HeaderIcon = styled(uilibGl.Flex) `
10025
+ position: absolute;
10026
+ top: 0;
10027
+ right: 0;
10028
+ align-items: center;
10029
+ justify-content: center;
10030
+ width: 7.625rem;
10031
+ height: 100%;
10032
+
10033
+ span[kind] {
10034
+ width: 4rem;
10035
+
10036
+ :after {
10037
+ font-size: 4rem;
10038
+ color: rgba(255, 255, 255, 0.36);
10039
+ }
10040
+ }
10041
+
10042
+ span[kind]:after,
10043
+ path,
10044
+ line,
10045
+ circle {
10046
+ fill: rgba(255, 255, 255, 0.36);
10047
+ }
10048
+
10049
+ && > * {
10050
+ display: flex;
10051
+ align-items: center;
10052
+ height: 100%;
10053
+ }
10054
+ `;
10055
+ const BigIconHeaderMixin = styled.css `
10056
+ ${HeaderIcon} {
10057
+ min-width: 14rem;
10058
+ right: -3rem;
10059
+
10060
+ span[kind]:after {
10061
+ font-size: 14rem;
10062
+ }
10063
+ }
10064
+ `;
10065
+ const WithPaddingHeaderMixin = styled.css `
10066
+ ${Header} {
10067
+ width: 100%;
10068
+ margin: -0.5rem -0.5rem 0.5rem -0.5rem;
10069
+ }
10070
+ `;
10071
+ const BottomBlurHeaderMixin = styled.css `
10072
+ ${Header} {
10073
+ margin-bottom: 0;
10074
+
10075
+ &::before {
10076
+ -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 100%);
10077
+ mask-image: linear-gradient(to bottom, #000 0%, transparent 100%);
10078
+ }
10079
+ }
10080
+
10081
+ ${ImageContainerBg} {
10082
+ -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 100%);
10083
+ mask-image: linear-gradient(to bottom, #000 0%, transparent 100%);
10084
+ }
10085
+
10086
+ ${HeaderFrontView} {
10087
+ z-index: 12;
10088
+ }
10089
+ `;
10090
+ const BackgroundHeaderWrapper = styled.div `
10091
+ ${Header} {
10092
+ position: relative;
10093
+ width: calc(100% + 1rem);
10094
+ height: ${({ $height }) => $height ? `${$height}px` : "auto"};
10095
+ margin: -1rem -1rem 1rem -1rem;
10096
+ border-radius: 0.5rem;
10097
+ background: transparent;
10098
+ overflow: hidden;
10099
+
10100
+ &::before {
10101
+ content: "";
10102
+ position: absolute;
10103
+ inset: 0;
10104
+ z-index: 0;
10105
+ pointer-events: none;
10106
+ background: ${({ $bgColor }) => $bgColor || "linear-gradient(96.55deg, #FFFCD3 0%, #B4DC47 100%)"};
10107
+ }
10108
+ }
10109
+
10110
+ ${HeaderWrapperMixin};
10111
+
10112
+ ${({ $bigIcon }) => $bigIcon && BigIconHeaderMixin};
10113
+
10114
+ ${({ $withPadding }) => $withPadding && WithPaddingHeaderMixin};
10115
+
10116
+ ${({ $bottomBlur }) => $bottomBlur && BottomBlurHeaderMixin};
10117
+ `;
10118
+
10068
10119
  const FeatureCardBackgroundHeader = () => {
10069
10120
  const { themeName: pageThemeName } = useGlobalContext();
10070
10121
  const { config } = useWidgetConfig(exports.WidgetType.FeatureCard);
@@ -10320,8 +10371,6 @@ const ModalIcon = styled(uilibGl.IconButton) `
10320
10371
  }
10321
10372
  `;
10322
10373
 
10323
- const isEmptyElementValue = (value) => value === "" || value === null || value === undefined;
10324
-
10325
10374
  const isHiddenEmptyValue = ({ value, children, hideEmpty, renderElement, }) => {
10326
10375
  const valueElement = children?.find(item => item.id === "value");
10327
10376
  const renderedValue = valueElement ? renderElement({ id: "value" }) : null;
@@ -12407,6 +12456,112 @@ const useDiffPage = (type) => {
12407
12456
  return React.useMemo(() => isDiffPage, [isDiffPage]);
12408
12457
  };
12409
12458
 
12459
+ const SAVE_HOOK_RESULT_DURATION = 4000;
12460
+ const NOTIFICATION_ID_RADIX = 36;
12461
+ const NOTIFICATION_ID_LENGTH = 8;
12462
+ const isHookActive = (hook) => !!hook && (!!hook.resourceId || !!hook.fileName);
12463
+ const createSaveNotificationId = () => `save-hook-${Date.now()}-${Math.random().toString(NOTIFICATION_ID_RADIX).slice(2, NOTIFICATION_ID_LENGTH)}`;
12464
+
12465
+ const useAfterSave = (hook, buildPrototype) => {
12466
+ const { t, notification } = useGlobalContext();
12467
+ const { runTask } = useRemoteTask();
12468
+ return React.useCallback(async (input) => {
12469
+ if (!isHookActive(hook)) {
12470
+ return;
12471
+ }
12472
+ const notificationId = createSaveNotificationId();
12473
+ notification?.add({
12474
+ id: notificationId,
12475
+ title: t("afterSaveProgress", { ns: "dashboard", defaultValue: "Выполняется действие после сохранения" }),
12476
+ progress: true,
12477
+ duration: Number.MAX_SAFE_INTEGER,
12478
+ });
12479
+ try {
12480
+ const { status, log } = await runTask(buildPrototype(hook, input));
12481
+ if (status === api.RemoteTaskStatus.Completed) {
12482
+ notification?.update({
12483
+ id: notificationId,
12484
+ title: t("afterSaveSuccess", { ns: "dashboard", defaultValue: "Действие после сохранения выполнено" }),
12485
+ success: true,
12486
+ progress: false,
12487
+ duration: SAVE_HOOK_RESULT_DURATION,
12488
+ });
12489
+ return;
12490
+ }
12491
+ notification?.update({
12492
+ id: notificationId,
12493
+ title: t("afterSaveError", { ns: "dashboard", defaultValue: "Не удалось выполнить действие после сохранения" }),
12494
+ description: log,
12495
+ error: true,
12496
+ progress: false,
12497
+ duration: SAVE_HOOK_RESULT_DURATION,
12498
+ });
12499
+ }
12500
+ catch (error) {
12501
+ const description = error instanceof Error ? error.message : undefined;
12502
+ notification?.update({
12503
+ id: notificationId,
12504
+ title: t("afterSaveError", { ns: "dashboard", defaultValue: "Не удалось выполнить действие после сохранения" }),
12505
+ description,
12506
+ error: true,
12507
+ progress: false,
12508
+ duration: SAVE_HOOK_RESULT_DURATION,
12509
+ });
12510
+ }
12511
+ }, [hook, buildPrototype, runTask, notification, t]);
12512
+ };
12513
+
12514
+ const useBeforeSave = (hook, buildPrototype) => {
12515
+ const { t, notification } = useGlobalContext();
12516
+ const { runTask } = useRemoteTask();
12517
+ return React.useCallback(async (input) => {
12518
+ if (!isHookActive(hook)) {
12519
+ return true;
12520
+ }
12521
+ const notificationId = createSaveNotificationId();
12522
+ notification?.add({
12523
+ id: notificationId,
12524
+ title: t("beforeSaveProgress", { ns: "dashboard", defaultValue: "Выполняется проверка перед сохранением" }),
12525
+ progress: true,
12526
+ duration: Number.MAX_SAFE_INTEGER,
12527
+ });
12528
+ try {
12529
+ const { status, log } = await runTask(buildPrototype(hook, input));
12530
+ if (status === api.RemoteTaskStatus.Completed) {
12531
+ notification?.update({
12532
+ id: notificationId,
12533
+ title: t("beforeSaveSuccess", { ns: "dashboard", defaultValue: "Проверка перед сохранением пройдена" }),
12534
+ success: true,
12535
+ progress: false,
12536
+ duration: SAVE_HOOK_RESULT_DURATION,
12537
+ });
12538
+ return true;
12539
+ }
12540
+ notification?.update({
12541
+ id: notificationId,
12542
+ title: t("beforeSaveError", { ns: "dashboard", defaultValue: "Не удалось выполнить проверку перед сохранением" }),
12543
+ description: log,
12544
+ error: true,
12545
+ progress: false,
12546
+ duration: SAVE_HOOK_RESULT_DURATION,
12547
+ });
12548
+ return false;
12549
+ }
12550
+ catch (error) {
12551
+ const description = error instanceof Error ? error.message : undefined;
12552
+ notification?.update({
12553
+ id: notificationId,
12554
+ title: t("beforeSaveError", { ns: "dashboard", defaultValue: "Не удалось выполнить проверку перед сохранением" }),
12555
+ description,
12556
+ error: true,
12557
+ progress: false,
12558
+ duration: SAVE_HOOK_RESULT_DURATION,
12559
+ });
12560
+ return false;
12561
+ }
12562
+ }, [hook, buildPrototype, runTask, notification, t]);
12563
+ };
12564
+
12410
12565
  const useExpandableContainers = () => {
12411
12566
  const [expandedContainers, setExpandedContainers] = React.useState({});
12412
12567
  const expandContainer = React.useCallback((id, expanded) => {
@@ -12514,6 +12669,83 @@ const useExportPdf = (id, margin = 20) => {
12514
12669
  return { loading, onExport };
12515
12670
  };
12516
12671
 
12672
+ const useSavePrototypeBuilder = () => {
12673
+ const { ewktGeometry } = useGlobalContext();
12674
+ const { layerInfo, attributes, dataSources, filters: selectedFilters, } = useWidgetContext(exports.WidgetType.FeatureCard);
12675
+ const { projectInfo, dataSources: projectDataSources } = useWidgetContext(exports.WidgetType.Dashboard);
12676
+ const { currentPage } = useWidgetPage(exports.WidgetType.FeatureCard);
12677
+ return React.useCallback((hook, input) => {
12678
+ const resolvedParameters = applyQueryFilters({
12679
+ parameters: hook.parameters ?? {},
12680
+ filters: currentPage?.filters ?? [],
12681
+ selectedFilters,
12682
+ geometry: ewktGeometry,
12683
+ attributes,
12684
+ layerInfo,
12685
+ dataSources,
12686
+ projectDataSources,
12687
+ });
12688
+ const scriptParameters = {
12689
+ projectName: projectInfo?.name,
12690
+ layerName: layerInfo?.name,
12691
+ featureId: input.featureId,
12692
+ properties: input.changedProperties,
12693
+ ...resolvedParameters,
12694
+ };
12695
+ return {
12696
+ enabled: true,
12697
+ startIfPreviousError: true,
12698
+ startIfPreviousNotFinished: true,
12699
+ subTaskSettings: [
12700
+ {
12701
+ type: "pythonService",
12702
+ startParameters: {
12703
+ resourceId: hook.resourceId,
12704
+ fileName: hook.fileName,
12705
+ methodName: hook.methodName,
12706
+ parameters: scriptParameters,
12707
+ method: "pythonrunner/run",
12708
+ },
12709
+ },
12710
+ ],
12711
+ };
12712
+ }, [
12713
+ attributes,
12714
+ currentPage?.filters,
12715
+ dataSources,
12716
+ ewktGeometry,
12717
+ layerInfo,
12718
+ projectDataSources,
12719
+ projectInfo?.name,
12720
+ selectedFilters,
12721
+ ]);
12722
+ };
12723
+
12724
+ /**
12725
+ * Орchestrator-хук для серверных `beforeSave` / `afterSave` python-скриптов,
12726
+ * описанных в `layerInfo.configuration.editConfiguration.options` слоя.
12727
+ *
12728
+ * - `runBeforeSave(input)` — `Promise<boolean>`, resolves `false` если
12729
+ * серверная проверка не прошла; сохранение должно быть отменено.
12730
+ * - `runAfterSave(input)` — fire-and-forget после успешного save.
12731
+ *
12732
+ * Активируется только если у потребителя в GlobalProvider переданы
12733
+ * `api`, `notification` и `t`, а приложение обёрнуто в
12734
+ * `<ServerNotificationsProvider>` (для SignalR-подписки на прогресс).
12735
+ */
12736
+ const useFeatureSaveHooks = () => {
12737
+ const { layerInfo } = useWidgetContext(exports.WidgetType.FeatureCard);
12738
+ const options = React.useMemo(() => layerInfo?.configuration
12739
+ ?.editConfiguration?.options, [layerInfo?.configuration]);
12740
+ const buildPrototype = useSavePrototypeBuilder();
12741
+ const runBeforeSave = useBeforeSave(options?.beforeSave, buildPrototype);
12742
+ const runAfterSave = useAfterSave(options?.afterSave, buildPrototype);
12743
+ return {
12744
+ runBeforeSave,
12745
+ runAfterSave,
12746
+ };
12747
+ };
12748
+
12517
12749
  const getMinMaxFromStringValue = (items, value, current, type) => {
12518
12750
  const valueIndex = items.findIndex(item => item.value === (type === "min" ? value.min : value.max));
12519
12751
  const currentIndex = items.findIndex(item => item.value === (type === "min" ? value.min : value.max));
@@ -13793,6 +14025,7 @@ exports.PresentationPanelWrapper = PresentationPanelWrapper;
13793
14025
  exports.PresentationWrapper = PresentationWrapper;
13794
14026
  exports.ProgressContainer = ProgressContainer;
13795
14027
  exports.RoundedBackgroundContainer = RoundedBackgroundContainer;
14028
+ exports.SAVE_HOOK_RESULT_DURATION = SAVE_HOOK_RESULT_DURATION;
13796
14029
  exports.SERVER_NOTIFICATION_EVENT = SERVER_NOTIFICATION_EVENT;
13797
14030
  exports.STACK_BAR_TOTAL_HEIGHT = STACK_BAR_TOTAL_HEIGHT;
13798
14031
  exports.ServerNotificationsContext = ServerNotificationsContext;
@@ -13835,6 +14068,7 @@ exports.checkIsLoading = checkIsLoading;
13835
14068
  exports.createConfigLayer = createConfigLayer;
13836
14069
  exports.createConfigPage = createConfigPage;
13837
14070
  exports.createNewPageId = createNewPageId;
14071
+ exports.createSaveNotificationId = createSaveNotificationId;
13838
14072
  exports.createTreeNode = createTreeNode;
13839
14073
  exports.dateOptions = dateOptions;
13840
14074
  exports.debounce = debounce;
@@ -13896,6 +14130,7 @@ exports.hexToRgba = hexToRgba;
13896
14130
  exports.isEmptyElementValue = isEmptyElementValue;
13897
14131
  exports.isEmptyValue = isEmptyValue;
13898
14132
  exports.isHiddenEmptyValue = isHiddenEmptyValue;
14133
+ exports.isHookActive = isHookActive;
13899
14134
  exports.isLayerService = isLayerService;
13900
14135
  exports.isNotValidSelectedTab = isNotValidSelectedTab;
13901
14136
  exports.isNumeric = isNumeric;
@@ -13922,10 +14157,12 @@ exports.tooltipValueFromRelatedFeatures = tooltipValueFromRelatedFeatures;
13922
14157
  exports.transparentizeColor = transparentizeColor;
13923
14158
  exports.treeNodesToProjectItems = treeNodesToProjectItems;
13924
14159
  exports.updateDataSource = updateDataSource;
14160
+ exports.useAfterSave = useAfterSave;
13925
14161
  exports.useAppHeight = useAppHeight;
13926
14162
  exports.useAttachmentItems = useAttachmentItems;
13927
14163
  exports.useAttachmentPreviewImages = useAttachmentPreviewImages;
13928
14164
  exports.useAutoCompleteControl = useAutoCompleteControl;
14165
+ exports.useBeforeSave = useBeforeSave;
13929
14166
  exports.useChartChange = useChartChange;
13930
14167
  exports.useChartData = useChartData;
13931
14168
  exports.useContainerAttributes = useContainerAttributes;
@@ -13938,6 +14175,7 @@ exports.useDiffPage = useDiffPage;
13938
14175
  exports.useEditGroupAttributes = useEditGroupAttributes;
13939
14176
  exports.useExpandableContainers = useExpandableContainers;
13940
14177
  exports.useExportPdf = useExportPdf;
14178
+ exports.useFeatureSaveHooks = useFeatureSaveHooks;
13941
14179
  exports.useFetchImageWithAuth = useFetchImageWithAuth;
13942
14180
  exports.useFetchWithAuth = useFetchWithAuth;
13943
14181
  exports.useGetConfigLayer = useGetConfigLayer;
@@ -13956,7 +14194,9 @@ exports.usePythonSandbox = usePythonSandbox;
13956
14194
  exports.usePythonTask = usePythonTask;
13957
14195
  exports.useRedrawLayer = useRedrawLayer;
13958
14196
  exports.useRelatedDataSourceAttributes = useRelatedDataSourceAttributes;
14197
+ exports.useRemoteTask = useRemoteTask;
13959
14198
  exports.useRenderElement = useRenderElement;
14199
+ exports.useSavePrototypeBuilder = useSavePrototypeBuilder;
13960
14200
  exports.useServerNotificationsContext = useServerNotificationsContext;
13961
14201
  exports.useShownOtherItems = useShownOtherItems;
13962
14202
  exports.useToggle = useToggle;