@evergis/react 3.1.66 → 3.1.68

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,4 @@
1
+ import { RemoteTaskStatus } from '@evergis/api';
2
+ export declare const STATUS_TRANSLATION_KEYS: Partial<Record<RemoteTaskStatus, string>>;
3
+ export declare const STATUS_DEFAULT_VALUES: Partial<Record<RemoteTaskStatus, string>>;
4
+ export declare const STATUS_COLORS: Partial<Record<RemoteTaskStatus, string>>;
@@ -0,0 +1,3 @@
1
+ import { FC } from 'react';
2
+ import { LogDialogProps } from './types';
3
+ export declare const LogDialog: FC<LogDialogProps>;
@@ -0,0 +1,4 @@
1
+ export declare const StatusBadge: import('styled-components').StyledComponent<"div", any, import('@evergis/uilib-gl').FlexProps & {
2
+ $statusColor: string;
3
+ }, never>;
4
+ export declare const LogContainer: import('styled-components').StyledComponent<"div", any, {}, never>;
@@ -0,0 +1,8 @@
1
+ import { RemoteTaskStatus } from '@evergis/api';
2
+ export interface LogDialogProps {
3
+ isOpen: boolean;
4
+ onClose: () => void;
5
+ logs: string | null;
6
+ status: RemoteTaskStatus;
7
+ statusColors?: Record<RemoteTaskStatus, string>;
8
+ }
@@ -0,0 +1,6 @@
1
+ import { FC } from 'react';
2
+ interface TaskLogTerminalProps {
3
+ log?: string | Record<string, any>;
4
+ }
5
+ export declare const LogTerminal: FC<TaskLogTerminalProps>;
6
+ export {};
@@ -0,0 +1 @@
1
+ export declare const TerminalWrapper: import('styled-components').StyledComponent<"div", any, {}, never>;
@@ -9,9 +9,12 @@ export declare const usePythonTask: () => {
9
9
  methodName?: string;
10
10
  }) => Promise<void>;
11
11
  stopTask: () => Promise<void>;
12
- result: string;
12
+ openLog: () => void;
13
+ log: string;
13
14
  error: Error;
14
15
  status: RemoteTaskStatus;
15
16
  loading: boolean;
16
17
  executionTime: number;
18
+ isLogDialogOpen: boolean;
19
+ closeLog: () => void;
17
20
  };
package/dist/index.js CHANGED
@@ -24,6 +24,9 @@ require('mapbox-gl/dist/mapbox-gl.css');
24
24
  var react = require('swiper/react');
25
25
  var ReactMarkdown = require('react-markdown');
26
26
  var remarkGfm = require('remark-gfm');
27
+ var xterm = require('@xterm/xterm');
28
+ var addonFit = require('@xterm/addon-fit');
29
+ require('@xterm/xterm/css/xterm.css');
27
30
 
28
31
  const AddFeatureButton = ({ title, icon = "feature_add" /* , layerName, geometryType*/ }) => {
29
32
  // const [, handleAddFeature] = useFeatureCreator(layerName, geometryType);
@@ -4828,15 +4831,16 @@ const usePythonTask = () => {
4828
4831
  const { api: api$1, t } = useGlobalContext();
4829
4832
  const { addSubscription, connection, unsubscribeById } = useServerNotificationsContext();
4830
4833
  const [status, setStatus] = React.useState(api.RemoteTaskStatus.Unknown);
4831
- const [result, setResult] = React.useState(null);
4834
+ const [log, setLog] = React.useState(null);
4832
4835
  const [error, setError] = React.useState(null);
4833
4836
  const [loading, setLoading] = React.useState(false);
4834
4837
  const [executionTime, setExecutionTime] = React.useState(null);
4835
4838
  const [taskId, setTaskId] = React.useState(null);
4836
4839
  const [subscriptionId, setSubscriptionId] = React.useState(null);
4840
+ const [isLogDialogOpen, setIsLogDialogOpen] = React.useState(false);
4841
+ const logRef = React.useRef(null);
4837
4842
  const reset = React.useCallback(() => {
4838
4843
  setStatus(api.RemoteTaskStatus.Unknown);
4839
- setResult(null);
4840
4844
  setError(null);
4841
4845
  setLoading(false);
4842
4846
  setExecutionTime(null);
@@ -4846,6 +4850,7 @@ const usePythonTask = () => {
4846
4850
  const runTask = React.useCallback(async ({ resourceId, parameters, script, fileName, methodName, }) => {
4847
4851
  reset();
4848
4852
  setStatus(api.RemoteTaskStatus.Process);
4853
+ setLog(null);
4849
4854
  setLoading(true);
4850
4855
  const start = Date.now();
4851
4856
  let prototypeId = await api$1.remoteTaskManager.createTaskPrototype({
@@ -4870,7 +4875,7 @@ const usePythonTask = () => {
4870
4875
  const { id: newTaskId, success } = await api$1.remoteTaskManager.startTask1(prototypeId);
4871
4876
  if (!success) {
4872
4877
  setStatus(api.RemoteTaskStatus.Error);
4873
- setResult(t("taskRunFail", { ns: "devMode", defaultValue: "Не удалось запустить задачу" }));
4878
+ setLog(t("taskRunFail", { ns: "devMode", defaultValue: "Не удалось запустить задачу" }));
4874
4879
  setExecutionTime(Date.now() - start);
4875
4880
  setLoading(false);
4876
4881
  }
@@ -4883,10 +4888,8 @@ const usePythonTask = () => {
4883
4888
  const onNotification = ({ data }) => {
4884
4889
  if (data?.taskId === newTaskId) {
4885
4890
  setStatus(data.status);
4886
- setResult(`${data ? `${data}\n` : ""}${data.log || ""}`);
4887
- setError(null);
4891
+ setLog([logRef.current, data.log].filter(Boolean).join("\n\n"));
4888
4892
  setExecutionTime(Date.now() - start);
4889
- setLoading(false);
4890
4893
  setTaskId([api.RemoteTaskStatus.Completed, api.RemoteTaskStatus.Error].includes(data.status)
4891
4894
  ? undefined
4892
4895
  : newTaskId);
@@ -4906,7 +4909,28 @@ const usePythonTask = () => {
4906
4909
  reset();
4907
4910
  unsubscribeById(subscriptionId);
4908
4911
  }, [api$1, reset, unsubscribeById, taskId, subscriptionId]);
4909
- return { taskId, runTask, stopTask, result, error, status, loading, executionTime };
4912
+ const openLog = React.useCallback(() => {
4913
+ setIsLogDialogOpen(true);
4914
+ }, []);
4915
+ const closeLog = React.useCallback(() => {
4916
+ setIsLogDialogOpen(false);
4917
+ }, []);
4918
+ React.useEffect(() => {
4919
+ logRef.current = log;
4920
+ }, [log]);
4921
+ return {
4922
+ taskId,
4923
+ runTask,
4924
+ stopTask,
4925
+ openLog,
4926
+ log,
4927
+ error,
4928
+ status,
4929
+ loading,
4930
+ executionTime,
4931
+ isLogDialogOpen,
4932
+ closeLog,
4933
+ };
4910
4934
  };
4911
4935
 
4912
4936
  const useAppHeight = () => {
@@ -6738,6 +6762,172 @@ const UploadContainer = React.memo(({ type, elementConfig, renderElement }) => {
6738
6762
  return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && renderElement({ id: "uploader", wrap: false })] }));
6739
6763
  });
6740
6764
 
6765
+ const StatusBadge = styled(uilibGl.Flex) `
6766
+ margin-bottom: 1rem;
6767
+ padding: 0.5rem 1rem;
6768
+ background-color: ${({ $statusColor }) => $statusColor};
6769
+ color: ${({ theme }) => theme.palette.iconContrast};
6770
+ border-radius: 0.25rem;
6771
+ `;
6772
+ styled.div `
6773
+ flex: 1;
6774
+ padding: 1rem;
6775
+ background-color: ${({ theme }) => theme.palette.element};
6776
+ color: ${({ theme }) => theme.palette.textPrimary};
6777
+ font-family: monospace;
6778
+ font-size: 0.875rem;
6779
+ overflow: auto;
6780
+ white-space: pre-wrap;
6781
+ word-break: break-word;
6782
+ border-radius: 0.25rem;
6783
+ max-height: 31.25rem;
6784
+ `;
6785
+
6786
+ const TerminalWrapper = styled.div `
6787
+ flex: 1;
6788
+ overflow: hidden;
6789
+ padding: 0;
6790
+ margin: 1rem;
6791
+ box-sizing: border-box;
6792
+ min-height: 0;
6793
+
6794
+ & && .xterm-viewport {
6795
+ overflow-y: auto;
6796
+ }
6797
+ `;
6798
+
6799
+ const LogTerminal = ({ log }) => {
6800
+ const terminalRef = React.useRef(null);
6801
+ const xtermRef = React.useRef(null);
6802
+ const fitAddonRef = React.useRef(null);
6803
+ const previousLogRef = React.useRef("");
6804
+ const theme = styled.useTheme();
6805
+ // Initialize terminal
6806
+ React.useEffect(() => {
6807
+ if (!terminalRef.current)
6808
+ return;
6809
+ // Create terminal instance
6810
+ const terminal = new xterm.Terminal({
6811
+ cursorBlink: false,
6812
+ fontSize: 12,
6813
+ fontFamily: '"Monaco", "Menlo", "Ubuntu Mono", "Consolas", "source-code-pro", monospace',
6814
+ theme: {
6815
+ background: theme.palette.devBackgroundDark,
6816
+ foreground: theme.palette.textPrimary,
6817
+ cursor: theme.palette.primary,
6818
+ },
6819
+ scrollback: 10000,
6820
+ convertEol: true,
6821
+ lineHeight: 1.5,
6822
+ });
6823
+ // Create fit addon
6824
+ const fitAddon = new addonFit.FitAddon();
6825
+ terminal.loadAddon(fitAddon);
6826
+ // Open terminal
6827
+ terminal.open(terminalRef.current);
6828
+ fitAddon.fit();
6829
+ // Store refs
6830
+ xtermRef.current = terminal;
6831
+ fitAddonRef.current = fitAddon;
6832
+ // Handle window resize
6833
+ const handleResize = () => {
6834
+ fitAddon.fit();
6835
+ };
6836
+ window.addEventListener("resize", handleResize);
6837
+ // Cleanup
6838
+ return () => {
6839
+ window.removeEventListener("resize", handleResize);
6840
+ terminal.dispose();
6841
+ xtermRef.current = null;
6842
+ fitAddonRef.current = null;
6843
+ };
6844
+ }, [theme]);
6845
+ // Update log content
6846
+ React.useEffect(() => {
6847
+ if (!xtermRef.current)
6848
+ return;
6849
+ // Handle different log types
6850
+ if (typeof log === "string") {
6851
+ // For string logs, only write the new content (append mode)
6852
+ const previousLog = previousLogRef.current;
6853
+ if (log !== previousLog) {
6854
+ if (log.startsWith(previousLog)) {
6855
+ // Log is accumulated - write only the new part
6856
+ const newContent = log.substring(previousLog.length);
6857
+ xtermRef.current.write(`${newContent.replace(/\n/g, "\r\n")}\r\n`);
6858
+ }
6859
+ else {
6860
+ // Log was replaced completely - clear and write all
6861
+ xtermRef.current.clear();
6862
+ xtermRef.current.write(`${log.replace(/\n/g, "\r\n")}\r\n`);
6863
+ }
6864
+ previousLogRef.current = log;
6865
+ }
6866
+ }
6867
+ else if (typeof log === "object") {
6868
+ // JSON object (results) - always replace
6869
+ xtermRef.current.clear();
6870
+ const formatted = JSON.stringify(log, null, 2);
6871
+ xtermRef.current.write(formatted.replace(/\n/g, "\r\n"));
6872
+ previousLogRef.current = "";
6873
+ }
6874
+ else if (!log) {
6875
+ // No log - clear terminal
6876
+ xtermRef.current.clear();
6877
+ previousLogRef.current = "";
6878
+ }
6879
+ // Scroll to bottom
6880
+ xtermRef.current.scrollToBottom();
6881
+ }, [log]);
6882
+ // Fit terminal on container resize
6883
+ React.useEffect(() => {
6884
+ if (!fitAddonRef.current)
6885
+ return;
6886
+ const resizeObserver = new ResizeObserver(() => {
6887
+ fitAddonRef.current?.fit();
6888
+ });
6889
+ if (terminalRef.current) {
6890
+ resizeObserver.observe(terminalRef.current);
6891
+ }
6892
+ return () => {
6893
+ resizeObserver.disconnect();
6894
+ };
6895
+ }, []);
6896
+ return jsxRuntime.jsx(TerminalWrapper, { ref: terminalRef });
6897
+ };
6898
+
6899
+ const STATUS_TRANSLATION_KEYS = {
6900
+ [api.RemoteTaskStatus.Process]: "taskProcess",
6901
+ [api.RemoteTaskStatus.Completed]: "taskCompleted",
6902
+ [api.RemoteTaskStatus.Error]: "taskError",
6903
+ [api.RemoteTaskStatus.Unknown]: "taskUnknown",
6904
+ };
6905
+ const STATUS_DEFAULT_VALUES = {
6906
+ [api.RemoteTaskStatus.Process]: "Остановить",
6907
+ [api.RemoteTaskStatus.Completed]: "Завершено",
6908
+ [api.RemoteTaskStatus.Error]: "Ошибка",
6909
+ [api.RemoteTaskStatus.Unknown]: "Неизвестно",
6910
+ };
6911
+ const STATUS_COLORS = {
6912
+ [api.RemoteTaskStatus.Process]: "#2196f3",
6913
+ [api.RemoteTaskStatus.Completed]: "#4caf50",
6914
+ [api.RemoteTaskStatus.Error]: "#f44336",
6915
+ [api.RemoteTaskStatus.Unknown]: "#757575",
6916
+ };
6917
+
6918
+ const LogDialog = ({ isOpen, onClose, logs, status, statusColors }) => {
6919
+ const { t } = useGlobalContext();
6920
+ const getStatusText = React.useCallback((status) => {
6921
+ const translationKey = STATUS_TRANSLATION_KEYS[status] || STATUS_TRANSLATION_KEYS[api.RemoteTaskStatus.Unknown];
6922
+ const defaultValue = STATUS_DEFAULT_VALUES[status] || STATUS_DEFAULT_VALUES[api.RemoteTaskStatus.Unknown];
6923
+ return t(translationKey, { ns: "dashboard", defaultValue });
6924
+ }, [t]);
6925
+ const getStatusColor = React.useCallback((status) => {
6926
+ return statusColors?.[status] || STATUS_COLORS[status] || STATUS_COLORS[api.RemoteTaskStatus.Unknown];
6927
+ }, [statusColors]);
6928
+ return (jsxRuntime.jsxs(uilibGl.Dialog, { isOpen: isOpen, onCloseRequest: onClose, modal: true, maxWidth: "800px", minWidth: "600px", minHeight: "400px", children: [jsxRuntime.jsx(uilibGl.DialogTitle, { children: jsxRuntime.jsxs(uilibGl.Flex, { justifyContent: "space-between", alignItems: "center", children: [jsxRuntime.jsx("span", { children: t("taskLogs", { ns: "dashboard", defaultValue: "Логи выполнения задачи" }) }), jsxRuntime.jsx(uilibGl.IconButton, { kind: "close", onClick: onClose })] }) }), jsxRuntime.jsx(uilibGl.DialogContent, { children: jsxRuntime.jsxs(uilibGl.Flex, { flexDirection: "column", height: "100%", marginBottom: "2rem", children: [jsxRuntime.jsxs(StatusBadge, { "$statusColor": getStatusColor(status), children: [t("taskStatus", { ns: "dashboard", defaultValue: "Статус" }), ": ", getStatusText(status)] }), jsxRuntime.jsx(LogTerminal, { log: logs || t("taskLogsEmpty", { ns: "dashboard", defaultValue: "Логи отсутствуют" }) })] }) })] }));
6929
+ };
6930
+
6741
6931
  const StatusWaitingButton = styled(uilibGl.WaitingButton) `
6742
6932
  ${({ status = api.RemoteTaskStatus.Unknown, statusColors }) => !!statusColors?.[status] && styled.css `
6743
6933
  transition: background-color ${uilibGl.transition.toggle};
@@ -6749,14 +6939,18 @@ const StatusWaitingButton = styled(uilibGl.WaitingButton) `
6749
6939
  `}
6750
6940
  `;
6751
6941
 
6752
- const TaskContainer = React.memo(({ type, elementConfig }) => {
6942
+ const TaskContainer = React.memo(({ type, elementConfig, renderElement }) => {
6753
6943
  const { t, ewktGeometry } = useGlobalContext();
6754
6944
  const { dataSources, filters: selectedFilters } = useWidgetContext(type);
6755
6945
  const { currentPage } = useWidgetPage(type);
6756
- const { taskId, runTask, stopTask, status, loading } = usePythonTask();
6946
+ const { taskId, runTask, stopTask, status, openLog, loading, isLogDialogOpen, closeLog, log } = usePythonTask();
6757
6947
  const { options } = elementConfig || {};
6758
6948
  const { title, relatedResources, center, icon, statusColors } = options || {};
6759
6949
  const onClick = React.useCallback(async () => {
6950
+ if (taskId) {
6951
+ await stopTask();
6952
+ return;
6953
+ }
6760
6954
  await Promise.all(relatedResources.map(({ resourceId, parameters, script, fileName, methodName }) => {
6761
6955
  const newParams = applyQueryFilters({
6762
6956
  parameters,
@@ -6767,8 +6961,8 @@ const TaskContainer = React.memo(({ type, elementConfig }) => {
6767
6961
  });
6768
6962
  return runTask({ resourceId, parameters: newParams, script, fileName, methodName });
6769
6963
  }));
6770
- }, [currentPage.filters, dataSources, ewktGeometry, relatedResources, runTask, selectedFilters]);
6771
- return (jsxRuntime.jsxs(uilibGl.Flex, { justifyContent: center ? "center" : "flex-start", children: [jsxRuntime.jsxs(StatusWaitingButton, { primary: true, status: status, statusColors: statusColors, isWaiting: loading, disabled: !relatedResources?.length, onClick: onClick, children: [icon && jsxRuntime.jsx(uilibGl.FlexSpan, { marginRight: "0.5rem", children: jsxRuntime.jsx(uilibGl.Icon, { kind: icon }) }), title || t("run", { ns: "dashboard", defaultValue: "Запуск" })] }), !!taskId && (jsxRuntime.jsx(uilibGl.IconButton, { kind: "stop", onClick: stopTask }))] }));
6964
+ }, [currentPage.filters, dataSources, ewktGeometry, relatedResources, runTask, selectedFilters, stopTask, taskId]);
6965
+ 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.jsxs(StatusWaitingButton, { primary: true, status: status, statusColors: statusColors, isWaiting: loading || !!taskId, disabled: !relatedResources?.length, onClick: onClick, children: [icon && jsxRuntime.jsx(uilibGl.FlexSpan, { marginRight: "0.5rem", children: jsxRuntime.jsx(uilibGl.Icon, { kind: icon }) }), title || t("run", { ns: "dashboard", defaultValue: "Запуск" })] }), !!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 })] }))] })] }));
6772
6966
  });
6773
6967
 
6774
6968
  const containerComponents = {
@@ -7598,27 +7792,30 @@ const StyledIcon = styled(uilibGl.Icon) `
7598
7792
 
7599
7793
  const ElementIcon = React.memo(({ type, elementConfig }) => {
7600
7794
  const { attributes } = useWidgetContext(type);
7601
- const { value, attributeName, options } = elementConfig || {};
7795
+ const { value, attributeName, options, style } = elementConfig || {};
7602
7796
  const { fontSize, fontColor } = options || {};
7603
7797
  const iconValue = React.useMemo(() => (attributeName ? attributes?.find(item => item.name === attributeName)?.value : value), [attributeName, attributes, value]);
7604
- return jsxRuntime.jsx(StyledIcon, { kind: iconValue, fontSize: fontSize, fontColor: fontColor });
7798
+ return jsxRuntime.jsx(StyledIcon, { kind: iconValue, fontSize: fontSize, fontColor: fontColor, style: style });
7605
7799
  });
7606
7800
 
7607
7801
  const ElementImage = React.memo(({ type, elementConfig }) => {
7608
7802
  const { attributes } = useWidgetContext(type);
7609
- const { value, attributeName, options } = elementConfig || {};
7803
+ const { value, attributeName, options, style } = elementConfig || {};
7610
7804
  const { width } = options || {};
7611
7805
  const firstImage = React.useMemo(() => {
7612
7806
  if (value) {
7613
- return value.toString();
7807
+ return getResourceUrl(value.toString());
7614
7808
  }
7615
7809
  if (!attributeName || Array.isArray(attributeName)) {
7616
7810
  return null;
7617
7811
  }
7618
7812
  const attribute = attributes?.find(item => item.name === attributeName);
7619
- return attribute?.value?.split(";")?.[0];
7813
+ const imageUrl = attribute?.value?.split(";")?.[0];
7814
+ if (!imageUrl)
7815
+ return null;
7816
+ return getResourceUrl(imageUrl);
7620
7817
  }, [attributeName, attributes, value]);
7621
- return firstImage ? jsxRuntime.jsx("img", { src: firstImage, alt: firstImage, width: width }) : null;
7818
+ return firstImage ? jsxRuntime.jsx("img", { src: firstImage, alt: firstImage, width: width, style: style }) : null;
7622
7819
  });
7623
7820
 
7624
7821
  const ElementLegend = React.memo(({ type, element, elementConfig }) => {