@evergis/react 4.0.62 → 4.0.64

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.
@@ -416,7 +416,7 @@ export interface TabsContainerConfig extends Omit<ConfigContainerChild, "options
416
416
  export interface TabsContainerProps extends Omit<ContainerProps, "elementConfig"> {
417
417
  elementConfig?: TabsContainerConfig;
418
418
  }
419
- export type TaskContainerOptions = Pick<ConfigOptions, "title" | "relatedResources" | "center" | "icon" | "statusColors" | "responseFilters">;
419
+ export type TaskContainerOptions = Pick<ConfigOptions, "title" | "relatedResources" | "center" | "icon" | "statusColors" | "responseFilters" | "useNotifications">;
420
420
  export interface TaskContainerConfig extends Omit<ConfigContainerChild, "options" | "templateName"> {
421
421
  templateName?: ContainerTemplate.Task;
422
422
  options?: TaskContainerOptions;
@@ -2,6 +2,7 @@ import { RemoteTaskStatus } from '@evergis/api';
2
2
  export interface LogDialogProps {
3
3
  isOpen: boolean;
4
4
  onClose: () => void;
5
+ onMinimize?: () => void;
5
6
  logs: string | null;
6
7
  status: RemoteTaskStatus;
7
8
  statusColors?: Record<RemoteTaskStatus, string>;
@@ -0,0 +1,12 @@
1
+ import { RemoteTaskStatus } from '@evergis/api';
2
+ interface UseTaskNotificationsParams {
3
+ enabled: boolean;
4
+ suppressed: boolean;
5
+ status: RemoteTaskStatus;
6
+ lastMessage: string | null;
7
+ openLog: () => void;
8
+ }
9
+ export declare const useTaskNotifications: ({ enabled, suppressed, status, lastMessage, openLog, }: UseTaskNotificationsParams) => {
10
+ setNotificationDismissed: import('react').Dispatch<import('react').SetStateAction<boolean>>;
11
+ };
12
+ export {};
@@ -0,0 +1,2 @@
1
+ import { ReactNode } from 'react';
2
+ export declare const clampDescription: (text: string) => ReactNode;
@@ -5,4 +5,11 @@ export declare const useGlobalContext: () => {
5
5
  themeName: import('../../..').ThemeName;
6
6
  api: import('@evergis/api').Api;
7
7
  ewktGeometry: string;
8
+ notification: {
9
+ add: (item: import('@evergis/uilib-gl').INotificationItem) => void;
10
+ update: (patch: Partial<import('@evergis/uilib-gl').INotificationItem> & {
11
+ id: string;
12
+ }) => void;
13
+ close: (id: string) => void;
14
+ };
8
15
  };
@@ -262,6 +262,7 @@ export interface ConfigMiscOptions {
262
262
  tabId?: string;
263
263
  downloadById?: string;
264
264
  parentResourceId?: string;
265
+ useNotifications?: boolean;
265
266
  }
266
267
  /**
267
268
  * Flat-объединение всех доменных миксинов. Существующие места, использующие
@@ -1,6 +1,7 @@
1
1
  import { PropsWithChildren } from 'react';
2
2
  import { i18n } from 'i18next';
3
3
  import { Api } from '@evergis/api';
4
+ import { INotificationItem } from '@evergis/uilib-gl';
4
5
  import { ThemeName } from '../../types';
5
6
  export type GlobalContextProps = PropsWithChildren<{
6
7
  t?: i18n["t"];
@@ -8,4 +9,11 @@ export type GlobalContextProps = PropsWithChildren<{
8
9
  ewktGeometry?: string;
9
10
  themeName?: ThemeName;
10
11
  api?: Api;
12
+ notification?: {
13
+ add: (item: INotificationItem) => void;
14
+ update: (patch: Partial<INotificationItem> & {
15
+ id: string;
16
+ }) => void;
17
+ close: (id: string) => void;
18
+ };
11
19
  }>;
@@ -11,6 +11,7 @@ export declare const usePythonTask: () => {
11
11
  stopTask: () => Promise<void>;
12
12
  openLog: () => void;
13
13
  log: string;
14
+ lastMessage: string;
14
15
  error: Error;
15
16
  status: RemoteTaskStatus;
16
17
  loading: boolean;
package/dist/index.js CHANGED
@@ -4217,7 +4217,7 @@ const useAttachmentItems = ({ type, elementConfig, valueOverride, }) => {
4217
4217
  };
4218
4218
 
4219
4219
  const useGlobalContext = () => {
4220
- const { t, language, themeName, api, ewktGeometry } = React.useContext(GlobalContext) || {};
4220
+ const { t, language, themeName, api, ewktGeometry, notification } = React.useContext(GlobalContext) || {};
4221
4221
  const translate = React.useCallback((value, options) => {
4222
4222
  if (t)
4223
4223
  return t(value, options);
@@ -4229,7 +4229,8 @@ const useGlobalContext = () => {
4229
4229
  themeName,
4230
4230
  api,
4231
4231
  ewktGeometry,
4232
- }), [language, translate, api, ewktGeometry, themeName]);
4232
+ notification,
4233
+ }), [language, translate, api, ewktGeometry, themeName, notification]);
4233
4234
  };
4234
4235
 
4235
4236
  const GRID_TILE_SIZE = "4.5rem";
@@ -5622,6 +5623,7 @@ const usePythonTask = () => {
5622
5623
  const { preparePythonSandbox } = usePythonSandbox();
5623
5624
  const [status, setStatus] = React.useState(api.RemoteTaskStatus.Unknown);
5624
5625
  const [log, setLog] = React.useState(null);
5626
+ const [lastMessage, setLastMessage] = React.useState(null);
5625
5627
  const [error, setError] = React.useState(null);
5626
5628
  const [loading, setLoading] = React.useState(false);
5627
5629
  const [executionTime, setExecutionTime] = React.useState(null);
@@ -5629,7 +5631,6 @@ const usePythonTask = () => {
5629
5631
  const [subscriptionId, setSubscriptionId] = React.useState(null);
5630
5632
  const [isLogDialogOpen, setIsLogDialogOpen] = React.useState(false);
5631
5633
  const [result, setResult] = React.useState(null);
5632
- const logRef = React.useRef(null);
5633
5634
  const reset = React.useCallback(() => {
5634
5635
  setStatus(api.RemoteTaskStatus.Unknown);
5635
5636
  setError(null);
@@ -5643,6 +5644,7 @@ const usePythonTask = () => {
5643
5644
  reset();
5644
5645
  setStatus(api.RemoteTaskStatus.Process);
5645
5646
  setLog(null);
5647
+ setLastMessage(null);
5646
5648
  setLoading(true);
5647
5649
  const start = Date.now();
5648
5650
  let prototypeId = await api$1.remoteTaskManager.createTaskPrototype({
@@ -5682,7 +5684,9 @@ const usePythonTask = () => {
5682
5684
  if (data?.taskId === newTaskId) {
5683
5685
  const isFinished = [api.RemoteTaskStatus.Completed, api.RemoteTaskStatus.Error].includes(data.status);
5684
5686
  setStatus(data.status);
5685
- setLog([logRef.current, data.log].filter(Boolean).join("\n"));
5687
+ setLog(prev => [prev, data.log].filter(Boolean).join(""));
5688
+ if (data.log)
5689
+ setLastMessage(data.log);
5686
5690
  setExecutionTime(Date.now() - start);
5687
5691
  setLoading(false);
5688
5692
  setTaskId(isFinished ? null : newTaskId);
@@ -5726,15 +5730,13 @@ const usePythonTask = () => {
5726
5730
  const closeLog = React.useCallback(() => {
5727
5731
  setIsLogDialogOpen(false);
5728
5732
  }, []);
5729
- React.useEffect(() => {
5730
- logRef.current = log;
5731
- }, [log]);
5732
5733
  return {
5733
5734
  taskId,
5734
5735
  runTask,
5735
5736
  stopTask,
5736
5737
  openLog,
5737
5738
  log,
5739
+ lastMessage,
5738
5740
  error,
5739
5741
  status,
5740
5742
  loading,
@@ -7635,17 +7637,17 @@ const STATUS_ICONS = {
7635
7637
  [api.RemoteTaskStatus.Unknown]: "play",
7636
7638
  };
7637
7639
 
7638
- const LogDialog = ({ isOpen, onClose, logs, status, statusColors }) => {
7640
+ const LogDialog = ({ isOpen, onClose, onMinimize, logs, status, statusColors }) => {
7639
7641
  const { t } = useGlobalContext();
7640
- const getStatusTitle = React.useCallback((status) => {
7641
- const translationKey = STATUS_TRANSLATION_KEYS[status] || STATUS_TRANSLATION_KEYS[api.RemoteTaskStatus.Unknown];
7642
- const defaultValue = STATUS_TITLES[status] || STATUS_TITLES[api.RemoteTaskStatus.Unknown];
7642
+ const getStatusTitle = React.useCallback((currentStatus) => {
7643
+ const translationKey = STATUS_TRANSLATION_KEYS[currentStatus] || STATUS_TRANSLATION_KEYS[api.RemoteTaskStatus.Unknown];
7644
+ const defaultValue = STATUS_TITLES[currentStatus] || STATUS_TITLES[api.RemoteTaskStatus.Unknown];
7643
7645
  return t(translationKey, { ns: "dashboard", defaultValue });
7644
7646
  }, [t]);
7645
- const getStatusColor = React.useCallback((status) => {
7646
- return statusColors?.[status] || STATUS_COLORS[status] || STATUS_COLORS[api.RemoteTaskStatus.Unknown];
7647
+ const getStatusColor = React.useCallback((currentStatus) => {
7648
+ return statusColors?.[currentStatus] || STATUS_COLORS[currentStatus] || STATUS_COLORS[api.RemoteTaskStatus.Unknown];
7647
7649
  }, [statusColors]);
7648
- 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.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: "Логи отсутствуют" }) }) }) })] }));
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: "Логи отсутствуют" }) }) }) })] }));
7649
7651
  };
7650
7652
 
7651
7653
  exports.ThemeName = void 0;
@@ -7708,14 +7710,131 @@ const buildFiltersFromResponse = (responseFilters, result) => {
7708
7710
  return Object.keys(filters).length ? filters : null;
7709
7711
  };
7710
7712
 
7713
+ const ClampedText = styled.div `
7714
+ display: -webkit-box;
7715
+ -webkit-line-clamp: 2;
7716
+ -webkit-box-orient: vertical;
7717
+ overflow: hidden;
7718
+ text-overflow: ellipsis;
7719
+ word-break: break-word;
7720
+ `;
7721
+ const clampDescription = (text) => React.createElement(ClampedText, null, text);
7722
+
7723
+ const RUN_ID_RADIX = 36;
7724
+ const RUN_ID_LENGTH = 8;
7725
+ const generateRunId = () => `task-${Date.now()}-${Math.random().toString(RUN_ID_RADIX).slice(2, RUN_ID_LENGTH)}`;
7726
+ const useTaskNotifications = ({ enabled, suppressed, status, lastMessage, openLog, }) => {
7727
+ const { t, notification } = useGlobalContext();
7728
+ const runIdRef = React.useRef(null);
7729
+ const prevStatusRef = React.useRef(api.RemoteTaskStatus.Unknown);
7730
+ const isVisibleRef = React.useRef(false);
7731
+ const [dismissed, setDismissed] = React.useState(false);
7732
+ React.useEffect(() => {
7733
+ if (!enabled) {
7734
+ if (runIdRef.current) {
7735
+ notification?.close(runIdRef.current);
7736
+ runIdRef.current = null;
7737
+ }
7738
+ isVisibleRef.current = false;
7739
+ prevStatusRef.current = status;
7740
+ return;
7741
+ }
7742
+ if (status === api.RemoteTaskStatus.Unknown) {
7743
+ prevStatusRef.current = status;
7744
+ return;
7745
+ }
7746
+ const isFreshRun = status === api.RemoteTaskStatus.Process && prevStatusRef.current !== api.RemoteTaskStatus.Process;
7747
+ if (isFreshRun) {
7748
+ runIdRef.current = generateRunId();
7749
+ isVisibleRef.current = false;
7750
+ setDismissed(false);
7751
+ }
7752
+ if (!runIdRef.current) {
7753
+ runIdRef.current = generateRunId();
7754
+ }
7755
+ const id = runIdRef.current;
7756
+ const description = clampDescription(lastMessage || t("taskStarted", { ns: "dashboard", defaultValue: "Запуск задачи…" }));
7757
+ const base = {
7758
+ id,
7759
+ title: "",
7760
+ description,
7761
+ duration: Number.MAX_SAFE_INTEGER,
7762
+ ...(lastMessage ? {
7763
+ button: t("details", { ns: "dashboard", defaultValue: "Подробнее" }),
7764
+ buttonIcon: "info",
7765
+ buttonColor: "primary",
7766
+ onButtonClick: openLog,
7767
+ } : {}),
7768
+ };
7769
+ let item = null;
7770
+ if (status === api.RemoteTaskStatus.Process) {
7771
+ item = {
7772
+ ...base,
7773
+ title: t("taskInProgress", { ns: "dashboard", defaultValue: "Выполняется" }),
7774
+ progress: true,
7775
+ };
7776
+ }
7777
+ else if (status === api.RemoteTaskStatus.Completed) {
7778
+ item = {
7779
+ ...base,
7780
+ title: t("taskCompleted", { ns: "dashboard", defaultValue: "Выполнено" }),
7781
+ success: true,
7782
+ progress: false,
7783
+ };
7784
+ }
7785
+ else if (status === api.RemoteTaskStatus.Error) {
7786
+ item = {
7787
+ ...base,
7788
+ title: t("taskError", { ns: "dashboard", defaultValue: "Ошибка" }),
7789
+ error: true,
7790
+ progress: false,
7791
+ };
7792
+ }
7793
+ if (!item) {
7794
+ prevStatusRef.current = status;
7795
+ return;
7796
+ }
7797
+ if (suppressed || dismissed) {
7798
+ if (isVisibleRef.current) {
7799
+ notification?.close(id);
7800
+ isVisibleRef.current = false;
7801
+ }
7802
+ }
7803
+ else if (isVisibleRef.current) {
7804
+ notification?.update(item);
7805
+ }
7806
+ else {
7807
+ notification?.add(item);
7808
+ isVisibleRef.current = true;
7809
+ }
7810
+ prevStatusRef.current = status;
7811
+ }, [enabled, suppressed, dismissed, status, lastMessage, openLog, t, notification]);
7812
+ return { setNotificationDismissed: setDismissed };
7813
+ };
7814
+
7711
7815
  const TaskContainer = React.memo(({ type, elementConfig, renderElement }) => {
7712
7816
  const { t, ewktGeometry } = useGlobalContext();
7713
7817
  const { dataSources, filters: selectedFilters, attributes, layerInfo, changeFilters, } = useWidgetContext(type);
7714
7818
  const { dataSources: projectDataSources } = useWidgetContext(exports.WidgetType.Dashboard);
7715
7819
  const { currentPage } = useWidgetPage(type);
7716
- const { taskId, runTask, stopTask, status, openLog, loading, isLogDialogOpen, closeLog, log, result, } = usePythonTask();
7820
+ const { taskId, runTask, stopTask, status, openLog, loading, isLogDialogOpen, closeLog, log, lastMessage, result, } = usePythonTask();
7717
7821
  const { options } = elementConfig || {};
7718
- const { title, relatedResources, center, icon, statusColors, responseFilters } = options || {};
7822
+ const { title, relatedResources, center, icon, statusColors, responseFilters, useNotifications, } = options || {};
7823
+ const { setNotificationDismissed } = useTaskNotifications({
7824
+ enabled: !!useNotifications,
7825
+ suppressed: isLogDialogOpen,
7826
+ status,
7827
+ lastMessage,
7828
+ openLog,
7829
+ });
7830
+ const onCloseLog = React.useCallback(() => {
7831
+ setNotificationDismissed(true);
7832
+ closeLog();
7833
+ }, [closeLog, setNotificationDismissed]);
7834
+ const onMinimizeLog = React.useCallback(() => {
7835
+ setNotificationDismissed(false);
7836
+ closeLog();
7837
+ }, [closeLog, setNotificationDismissed]);
7719
7838
  React.useEffect(() => {
7720
7839
  const filtersToApply = buildFiltersFromResponse(responseFilters, result);
7721
7840
  if (filtersToApply)
@@ -7740,7 +7859,7 @@ const TaskContainer = React.memo(({ type, elementConfig, renderElement }) => {
7740
7859
  return runTask({ resourceId, parameters: newParams, script, fileName, methodName });
7741
7860
  }));
7742
7861
  }, [attributes, currentPage.filters, dataSources, ewktGeometry, layerInfo, projectDataSources, relatedResources, runTask, selectedFilters, stopTask, taskId]);
7743
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), jsxRuntime.jsxs(uilibGl.Flex, { justifyContent: center ? "center" : "flex-start", children: [jsxRuntime.jsx(StatusWaitingButton, { title: title || t("run", { ns: "dashboard", defaultValue: "Запуск" }), icon: icon, status: status, statusColors: statusColors, isWaiting: loading || !!taskId, isDisabled: !relatedResources?.length, onClick: onClick }), !!(log || taskId) && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(uilibGl.IconButton, { kind: "info", onClick: openLog }), jsxRuntime.jsx(LogDialog, { logs: log, status: status, statusColors: statusColors, isOpen: isLogDialogOpen, onClose: closeLog })] }))] })] }));
7862
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), jsxRuntime.jsxs(uilibGl.Flex, { justifyContent: center ? "center" : "flex-start", children: [jsxRuntime.jsx(StatusWaitingButton, { title: title || t("run", { ns: "dashboard", defaultValue: "Запуск" }), icon: icon, status: status, statusColors: statusColors, isWaiting: loading || !!taskId, isDisabled: !relatedResources?.length, onClick: onClick }), !!(log || taskId) && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(uilibGl.IconButton, { kind: "info", onClick: openLog }), jsxRuntime.jsx(LogDialog, { logs: log, status: status, statusColors: statusColors, isOpen: isLogDialogOpen, onClose: onCloseLog, onMinimize: useNotifications ? onMinimizeLog : undefined })] }))] })] }));
7744
7863
  });
7745
7864
 
7746
7865
  const EditContainer = ({ type, elementConfig, renderElement }) => {