seeder-st2110-components 1.2.4 → 1.2.5

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.esm.js CHANGED
@@ -1,8 +1,8 @@
1
- import { useState, useCallback, useEffect, memo, useMemo, useRef } from 'react';
1
+ import { useState, useCallback, useEffect, memo, useMemo, useRef, forwardRef, useContext, createContext } from 'react';
2
2
  import { useWebSocket, useInterval } from 'ahooks';
3
- import { Tooltip, Space, Flex, Divider, App, Modal, Form, Input, Alert, message, Dropdown, Spin, InputNumber, ConfigProvider, Badge, Switch, Select, Typography } from 'antd';
3
+ import { Tooltip, Space, Flex, Divider, App, Modal, Form, Input, Alert, message, Dropdown, Spin, Popover, Button, Popconfirm, Typography, Empty, ConfigProvider, Tree, InputNumber, Badge, Switch, Select } from 'antd';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
- import { LoadingOutlined, ExclamationCircleFilled } from '@ant-design/icons';
5
+ import { LoadingOutlined, ExclamationCircleFilled, FolderOpenOutlined, FolderOutlined } from '@ant-design/icons';
6
6
  import axios from 'axios';
7
7
 
8
8
  const useHardwareWebSocket = socketUrl => {
@@ -666,6 +666,571 @@ const useSystemOperations = function () {
666
666
  };
667
667
  };
668
668
 
669
+ const PopoverContent = /*#__PURE__*/forwardRef((_ref, ref) => {
670
+ let {
671
+ onClose,
672
+ onConfirm
673
+ } = _ref;
674
+ const [value, setValue] = useState('');
675
+ const handleConfirm = useCallback(() => {
676
+ onConfirm(value, () => setValue(''));
677
+ }, [confirm, value]);
678
+ const handleKeyDown = useCallback(e => {
679
+ if (e.key === 'Enter') {
680
+ handleConfirm();
681
+ }
682
+ }, [handleConfirm]);
683
+ return /*#__PURE__*/jsxs(Fragment, {
684
+ children: [/*#__PURE__*/jsx(Form.Item, {
685
+ label: "Folder Name",
686
+ style: {
687
+ marginBottom: 8
688
+ },
689
+ children: /*#__PURE__*/jsx(Input, {
690
+ ref: ref,
691
+ value: value,
692
+ onChange: e => setValue(e.target.value),
693
+ onKeyDown: handleKeyDown,
694
+ size: "small",
695
+ style: {
696
+ width: 120
697
+ },
698
+ autoFocus: true
699
+ })
700
+ }), /*#__PURE__*/jsxs(Flex, {
701
+ justify: "flex-end",
702
+ gap: "small",
703
+ children: [/*#__PURE__*/jsx(Button, {
704
+ type: "default",
705
+ size: "small",
706
+ onClick: onClose,
707
+ children: "Close"
708
+ }), /*#__PURE__*/jsx(Button, {
709
+ type: "primary",
710
+ size: "small",
711
+ onClick: handleConfirm,
712
+ children: "OK"
713
+ })]
714
+ })]
715
+ });
716
+ });
717
+ const EditablePopover = _ref2 => {
718
+ let {
719
+ children,
720
+ add
721
+ } = _ref2;
722
+ const inputRef = useRef(null);
723
+ const [open, setOpen] = useState(false);
724
+ useEffect(() => {
725
+ if (open && inputRef.current) {
726
+ const timer = setTimeout(() => {
727
+ inputRef.current?.focus();
728
+ }, 100);
729
+ return () => clearTimeout(timer);
730
+ }
731
+ }, [open]);
732
+
733
+ // 使用 open 属性控制浮层显示
734
+ const hide = useCallback(() => {
735
+ setOpen(false);
736
+ }, []);
737
+ const handleConfirm = useCallback((newTitle, callback) => {
738
+ add(newTitle);
739
+ callback?.();
740
+ hide();
741
+ }, [add, hide]);
742
+ const handleOpenChange = useCallback(newOpen => {
743
+ setOpen(newOpen);
744
+ }, []);
745
+ return /*#__PURE__*/jsx(Popover, {
746
+ content: /*#__PURE__*/jsx(PopoverContent, {
747
+ ref: inputRef,
748
+ onClose: hide,
749
+ onConfirm: handleConfirm
750
+ }),
751
+ trigger: "click",
752
+ open: open,
753
+ onOpenChange: handleOpenChange,
754
+ destroyOnHidden: true,
755
+ children: children
756
+ });
757
+ };
758
+ var EditablePopover$1 = /*#__PURE__*/memo(EditablePopover);
759
+
760
+ const EditableContext = /*#__PURE__*/createContext(null);
761
+ const TreeTitle = _ref => {
762
+ let {
763
+ ...props
764
+ } = _ref;
765
+ const [form] = Form.useForm();
766
+ return /*#__PURE__*/jsx(Form, {
767
+ form: form,
768
+ component: false,
769
+ children: /*#__PURE__*/jsx(EditableContext.Provider, {
770
+ value: form,
771
+ children: /*#__PURE__*/jsx(TreeTitleNode, {
772
+ ...props
773
+ })
774
+ })
775
+ });
776
+ };
777
+ const TreeTitleNode = _ref2 => {
778
+ let {
779
+ title,
780
+ nodeData,
781
+ handleSave,
782
+ handleDel,
783
+ handleAdd
784
+ } = _ref2;
785
+ const [editing, setEditing] = useState(false);
786
+ const inputRef = useRef(null);
787
+ const form = useContext(EditableContext);
788
+ useEffect(() => {
789
+ if (editing) {
790
+ inputRef.current?.focus();
791
+ }
792
+ }, [editing]);
793
+ const toggleEdit = useCallback(() => {
794
+ setEditing(prev => !prev);
795
+ form.setFieldsValue({
796
+ [nodeData.title]: nodeData.title
797
+ });
798
+ }, [form, nodeData.title]);
799
+ const save = useCallback(async () => {
800
+ try {
801
+ const values = await form.validateFields();
802
+ toggleEdit();
803
+ handleSave({
804
+ ...nodeData,
805
+ title: values[nodeData.title]
806
+ });
807
+ } catch (errInfo) {
808
+ console.error('Save failed:', errInfo);
809
+ }
810
+ }, [form, toggleEdit, handleSave, nodeData]);
811
+
812
+ // 新建文件夹
813
+ // 修改文件/文件夹
814
+ // 删除文件/文件夹
815
+ const renderIconNode = useCallback(() => {
816
+ return /*#__PURE__*/jsxs("span", {
817
+ className: "icon-node",
818
+ onClick: e => e.stopPropagation(),
819
+ children: [/*#__PURE__*/jsx(EditablePopover$1, {
820
+ add: newTitle => handleAdd(nodeData, newTitle),
821
+ children: /*#__PURE__*/jsx("div", {
822
+ className: "px-1",
823
+ title: "create",
824
+ children: /*#__PURE__*/jsx("i", {
825
+ className: "iconfont icon-jia"
826
+ })
827
+ })
828
+ }), !nodeData.isRoot && /*#__PURE__*/jsxs(Fragment, {
829
+ children: [/*#__PURE__*/jsx("div", {
830
+ className: "px-1",
831
+ onClick: toggleEdit,
832
+ title: "edit",
833
+ children: /*#__PURE__*/jsx("i", {
834
+ className: "iconfont icon-bianji"
835
+ })
836
+ }), /*#__PURE__*/jsx(Popconfirm, {
837
+ title: "Confirm to delete?",
838
+ onConfirm: () => handleDel(nodeData),
839
+ okText: "Yes",
840
+ cancelText: "No",
841
+ children: /*#__PURE__*/jsx("div", {
842
+ className: "px-1",
843
+ title: "delete",
844
+ children: /*#__PURE__*/jsx("i", {
845
+ className: "iconfont icon-jian"
846
+ })
847
+ })
848
+ })]
849
+ })]
850
+ });
851
+ }, [handleAdd, nodeData, title, toggleEdit, handleDel]);
852
+ const renderChildNode = useCallback(() => {
853
+ return editing ? /*#__PURE__*/jsx(Form.Item, {
854
+ style: {
855
+ margin: 0,
856
+ width: "100%"
857
+ },
858
+ name: nodeData.title,
859
+ children: /*#__PURE__*/jsx(Input, {
860
+ ref: inputRef,
861
+ onPressEnter: save,
862
+ onBlur: save,
863
+ autoComplete: "off",
864
+ size: "small"
865
+ })
866
+ }) : /*#__PURE__*/jsxs(Fragment, {
867
+ children: [/*#__PURE__*/jsx(Typography.Text, {
868
+ ellipsis: true,
869
+ style: {
870
+ width: "75%"
871
+ },
872
+ children: title
873
+ }), renderIconNode()]
874
+ });
875
+ }, [editing, nodeData.title, save, title, renderIconNode]);
876
+ return /*#__PURE__*/jsx("div", {
877
+ className: "tree-title",
878
+ children: renderChildNode()
879
+ });
880
+ };
881
+ var TreeTitle$1 = /*#__PURE__*/memo(TreeTitle);
882
+
883
+ const replaceRootPath = path => {
884
+ if (typeof path !== 'string') return '';
885
+ return path.startsWith('root/') ? path.slice(5) : path;
886
+ };
887
+
888
+ /**
889
+ * 递归转换目录结构为树形结构
890
+ * @param {Array} data - 原始数据
891
+ * @param {string} [basePath='0'] - 基础路径
892
+ * @param {string} [parentRoute] - 父级路由路径
893
+ * @returns {Array} 转换后的树形结构
894
+ */
895
+ const buildDirectoryTree = function (data) {
896
+ let basePath = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '0';
897
+ let parentRoute = arguments.length > 2 ? arguments[2] : undefined;
898
+ if (!Array.isArray(data)) return [];
899
+ return data.reduce((acc, item, index) => {
900
+ // 跳过文件类型,只处理目录
901
+ if (item.type === 'file') return acc;
902
+ const title = parentRoute ? item.name : item.sd_index || item.name;
903
+ const key = `${basePath}-${index}`;
904
+ const currentPath = parentRoute ? `${parentRoute}/${title}` : item.path || title;
905
+ const treeNode = {
906
+ title,
907
+ // 文件/文件夹名称
908
+ key,
909
+ icon: _ref => {
910
+ let {
911
+ expanded
912
+ } = _ref;
913
+ return expanded ? /*#__PURE__*/jsx(FolderOpenOutlined, {}) : /*#__PURE__*/jsx(FolderOutlined, {});
914
+ },
915
+ children: [],
916
+ isLeaf: false,
917
+ isRoot: Boolean(item.sd_index),
918
+ path: currentPath,
919
+ contents: (item.contents || []).filter(content => content.type === 'file'),
920
+ rawData: item // 保留原始数据
921
+ };
922
+
923
+ // 递归处理子目录
924
+ if (Array.isArray(item.contents) && item.contents.length > 0) {
925
+ treeNode.children = buildDirectoryTree(item.contents.filter(content => content.type !== 'file'), key, currentPath);
926
+ }
927
+ acc.push(treeNode);
928
+ return acc;
929
+ }, []);
930
+ };
931
+
932
+ /**
933
+ * 查找树中指定key的节点
934
+ * @param {Array} treeData - 树数据
935
+ * @param {string} targetKey - 要查找的key
936
+ * @returns {Array} 包含匹配节点的数组
937
+ */
938
+ const findTreeNode = (treeData, targetKey) => {
939
+ if (!Array.isArray(treeData) || typeof targetKey !== 'string') return [];
940
+ const result = [];
941
+ const stack = [...treeData];
942
+ while (stack.length) {
943
+ const node = stack.pop();
944
+ if (node.key === targetKey) {
945
+ result.push(node);
946
+ // 如果只需要第一个匹配项,可以在这里break
947
+ }
948
+ if (Array.isArray(node.children)) {
949
+ stack.push(...node.children);
950
+ }
951
+ }
952
+ return result;
953
+ };
954
+
955
+ /**
956
+ * 获取树中所有节点的keys
957
+ * @param {Array} treeData - 树数据
958
+ * @returns {Array} 包含所有节点keys的数组
959
+ */
960
+ const getAllNodeKeys = treeData => {
961
+ if (!Array.isArray(treeData)) return [];
962
+ const keys = [];
963
+ const stack = [...treeData];
964
+ while (stack.length) {
965
+ const node = stack.pop();
966
+ if (node.key != null) {
967
+ keys.push(node.key);
968
+ }
969
+ if (Array.isArray(node.children)) {
970
+ stack.push(...node.children);
971
+ }
972
+ }
973
+ return keys;
974
+ };
975
+
976
+ const useDirectoryTree = _ref => {
977
+ let {
978
+ getFolderData,
979
+ createFolder,
980
+ removeFolderFile,
981
+ renameFolderFile,
982
+ height = 760,
983
+ theme = {
984
+ components: {
985
+ Tree: {
986
+ titleHeight: 30
987
+ }
988
+ }
989
+ }
990
+ } = _ref;
991
+ const [treeState, setTreeState] = useState({
992
+ data: [],
993
+ selectedKeys: [],
994
+ expandedKeys: [],
995
+ currentPath: "",
996
+ contents: [],
997
+ loading: false
998
+ });
999
+ const [originTreeData, setOriginTreeData] = useState([]);
1000
+ const updateTreeState = partialState => {
1001
+ setTreeState(prev => ({
1002
+ ...prev,
1003
+ ...partialState
1004
+ }));
1005
+ };
1006
+
1007
+ // 错误处理
1008
+ const handleError = (error, operation) => {
1009
+ console.error(`${operation} ERROR`, error);
1010
+ // 可以添加通知等统一错误处理
1011
+ };
1012
+
1013
+ // 路径处理工具
1014
+ const pathUtils = {
1015
+ replaceRoot: path => replaceRootPath(path),
1016
+ getNewPath: (node, newTitle) => {
1017
+ const arr = node.path.split('/');
1018
+ arr[arr.length - 1] = newTitle;
1019
+ return arr.join('/');
1020
+ },
1021
+ isSamePath: (path1, path2) => {
1022
+ return pathUtils.replaceRoot(path1) === pathUtils.replaceRoot(path2);
1023
+ }
1024
+ };
1025
+
1026
+ // 节点查找工具
1027
+ const nodeUtils = {
1028
+ findNode: (treeData, key) => {
1029
+ return findTreeNode(treeData, key);
1030
+ }
1031
+ };
1032
+
1033
+ // 获取文件夹数据 文件夹名称为空时返回根目录数据
1034
+ const fetchFolderData = useCallback(async function () {
1035
+ let initialization = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
1036
+ try {
1037
+ const data = await getFolderData({
1038
+ folder: ""
1039
+ });
1040
+ if (!data?.directoryTree) {
1041
+ return null;
1042
+ }
1043
+ setOriginTreeData(data.directoryTree);
1044
+
1045
+ // 构建根节点并生成树结构
1046
+ const rootNode = {
1047
+ name: 'root',
1048
+ type: 'directory',
1049
+ contents: data.directoryTree
1050
+ };
1051
+ // 递归生成treenodes
1052
+ const treeNodes = buildDirectoryTree([rootNode]);
1053
+ const rootChildren = treeNodes[0]?.children || [];
1054
+ if (!rootChildren.length) {
1055
+ return null;
1056
+ }
1057
+ const firstChild = rootChildren[0];
1058
+ const newState = {
1059
+ data: rootChildren,
1060
+ ...(initialization && {
1061
+ selectedKeys: [firstChild?.key],
1062
+ expandedKeys: getAllNodeKeys(rootChildren),
1063
+ currentPath: firstChild?.path,
1064
+ contents: firstChild?.contents || []
1065
+ })
1066
+ };
1067
+ updateTreeState(newState);
1068
+ return rootChildren;
1069
+ } catch (error) {
1070
+ handleError(error, 'GET FOLDER DATA');
1071
+ }
1072
+ }, []);
1073
+
1074
+ // 初始化数据
1075
+ useEffect(() => {
1076
+ fetchFolderData(true);
1077
+ }, [fetchFolderData]);
1078
+
1079
+ // 创建文件夹
1080
+ const handleCreate = useCallback(async (node, newTitle) => {
1081
+ if (!newTitle?.trim()) return false;
1082
+ try {
1083
+ const path = pathUtils.replaceRoot(`${node.path}/${newTitle}`);
1084
+ await createFolder({
1085
+ path
1086
+ });
1087
+ const newTreeData = await fetchFolderData();
1088
+
1089
+ // 找到新增节点的父节点
1090
+ const parentNode = nodeUtils.findNode(newTreeData, node.key);
1091
+ if (parentNode?.[0]?.children) {
1092
+ // 通过 path 找到新增节点,得到新增节点的key
1093
+ const addedNode = parentNode[0].children.find(ch => pathUtils.isSamePath(ch.path, `root/${newTitle}`));
1094
+ if (addedNode) {
1095
+ updateTreeState({
1096
+ expandedKeys: [...treeState.expandedKeys, addedNode.key, parentNode[0].key]
1097
+ });
1098
+ }
1099
+ }
1100
+ } catch (error) {
1101
+ handleError(error, 'CREATE FOLDER');
1102
+ }
1103
+ }, [fetchFolderData, treeState.expandedKeys]);
1104
+
1105
+ // 删除文件/文件夹
1106
+ const handleRemove = useCallback(async node => {
1107
+ if (!node.path) return;
1108
+ try {
1109
+ await removeFolderFile({
1110
+ paths: [{
1111
+ path: pathUtils.replaceRoot(node.path)
1112
+ }]
1113
+ });
1114
+ // 如果删除的是当前选中节点,则重新初始化
1115
+ const shouldReinitialize = treeState.selectedKeys[0] === node.key;
1116
+ await fetchFolderData(shouldReinitialize);
1117
+ } catch (error) {
1118
+ handleError(error, 'REMOVE FOLDER/FILE');
1119
+ }
1120
+ }, [fetchFolderData, treeState.selectedKeys]);
1121
+
1122
+ // 修改文件/文件夹
1123
+ const handleRename = useCallback(async node => {
1124
+ const newPath = pathUtils.getNewPath(node, node.title);
1125
+ if (pathUtils.isSamePath(node.path, newPath)) return false;
1126
+ try {
1127
+ await renameFolderFile({
1128
+ old_path: pathUtils.replaceRoot(node.path),
1129
+ new_path: pathUtils.replaceRoot(newPath)
1130
+ });
1131
+ await fetchFolderData();
1132
+ } catch (error) {
1133
+ handleError(error, 'RENAME FOLDER/FILE');
1134
+ }
1135
+ }, [fetchFolderData]);
1136
+
1137
+ // 选择节点
1138
+ const onSelect = useCallback(async (keys, info) => {
1139
+ if (!keys.length || keys[0] === treeState.selectedKeys[0] && pathUtils.isSamePath(info.node.path, treeState.currentPath)) return;
1140
+ updateTreeState({
1141
+ selectedKeys: keys,
1142
+ currentPath: info.node.path,
1143
+ loading: true
1144
+ });
1145
+ try {
1146
+ // 模拟延迟加载
1147
+ // eslint-disable-next-line no-promise-executor-return
1148
+ await new Promise(resolve => setTimeout(resolve, 300));
1149
+ updateTreeState({
1150
+ contents: info.node.contents,
1151
+ loading: false
1152
+ });
1153
+ } catch (error) {
1154
+ handleError(error, 'SELECT NODE');
1155
+ updateTreeState({
1156
+ loading: false
1157
+ });
1158
+ }
1159
+ }, [treeState.selectedKeys, treeState.currentPath]);
1160
+
1161
+ // 展开节点
1162
+ const onExpand = useCallback(keys => {
1163
+ updateTreeState({
1164
+ expandedKeys: keys
1165
+ });
1166
+ }, []);
1167
+
1168
+ // 上传右侧文件或删除右侧文件 成功后的回调函数
1169
+ const updateFileContents = useCallback(async () => {
1170
+ // 找到当前选中的节点,更新当前节点的 contents
1171
+ const newTreeData = await fetchFolderData();
1172
+ const selectedNode = nodeUtils.findNode(newTreeData, treeState.selectedKeys[0]);
1173
+ if (selectedNode?.[0]) {
1174
+ updateTreeState({
1175
+ contents: selectedNode[0].contents
1176
+ });
1177
+ }
1178
+ }, [fetchFolderData, treeState.selectedKeys]);
1179
+
1180
+ // 删除文件
1181
+ const removeFile = useCallback(async url => {
1182
+ try {
1183
+ await removeFolderFile({
1184
+ path: url
1185
+ });
1186
+ await updateFileContents();
1187
+ } catch (error) {
1188
+ handleError(error, 'REMOVE FILE');
1189
+ }
1190
+ }, [updateFileContents]);
1191
+ const MemoizedTree = useMemo(() => {
1192
+ if (!treeState.data?.length) {
1193
+ return /*#__PURE__*/jsx(Empty, {
1194
+ image: Empty.PRESENTED_IMAGE_SIMPLE
1195
+ });
1196
+ }
1197
+ return /*#__PURE__*/jsx("div", {
1198
+ style: {
1199
+ paddingTop: 16
1200
+ },
1201
+ children: /*#__PURE__*/jsx(ConfigProvider, {
1202
+ theme: theme,
1203
+ children: /*#__PURE__*/jsx(Tree, {
1204
+ blockNode: true,
1205
+ showIcon: true,
1206
+ selectedKeys: treeState.selectedKeys,
1207
+ expandedKeys: treeState.expandedKeys,
1208
+ onSelect: onSelect,
1209
+ onExpand: onExpand,
1210
+ treeData: treeState.data,
1211
+ titleRender: nodeData => /*#__PURE__*/jsx(TreeTitle$1, {
1212
+ title: nodeData.title,
1213
+ nodeData: nodeData,
1214
+ handleSave: handleRename,
1215
+ handleDel: handleRemove,
1216
+ handleAdd: handleCreate
1217
+ }),
1218
+ height: height
1219
+ })
1220
+ })
1221
+ }, "folder-directory");
1222
+ }, [treeState.data, treeState.selectedKeys, treeState.expandedKeys]);
1223
+ return {
1224
+ directoryTree: MemoizedTree,
1225
+ contents,
1226
+ currentPath,
1227
+ updateFileContents,
1228
+ removeFile,
1229
+ loading,
1230
+ originTreeData
1231
+ };
1232
+ };
1233
+
669
1234
  const UpgradeManager = _ref => {
670
1235
  let {
671
1236
  menuItems = [],
@@ -2610,5 +3175,5 @@ const NetworkSettingsModal = _ref2 => {
2610
3175
  };
2611
3176
  var NetworkSettingsModal$1 = /*#__PURE__*/memo(NetworkSettingsModal);
2612
3177
 
2613
- export { AuthorizationModal, NetworkSettingsModal$1 as NetworkSettingsModal, PtpModal$1 as PtpModal, SystemOperations, UpgradeManager, useAuth, useHardwareUsage, useHardwareWebSocket, useSystemOperations, useUpgrade };
3178
+ export { AuthorizationModal, NetworkSettingsModal$1 as NetworkSettingsModal, PtpModal$1 as PtpModal, SystemOperations, UpgradeManager, useAuth, useDirectoryTree, useHardwareUsage, useHardwareWebSocket, useSystemOperations, useUpgrade };
2614
3179
  //# sourceMappingURL=index.esm.js.map