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