seeder-st2110-components 1.2.4 → 1.2.6

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