@ant-design/x
Version:
Craft AI-driven interfaces effortlessly
271 lines (250 loc) • 9.83 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _util = require("@rc-component/util");
var _antd = require("antd");
var _clsx = require("clsx");
var _react = _interopRequireWildcard(require("react"));
var _useProxyImperativeHandle = _interopRequireDefault(require("../_util/hooks/use-proxy-imperative-handle"));
var _useXComponentConfig = _interopRequireDefault(require("../_util/hooks/use-x-component-config"));
var _locale = require("../locale");
var _en_US = _interopRequireDefault(require("../locale/en_US"));
var _xProvider = require("../x-provider");
var _DirectoryTree = _interopRequireDefault(require("./DirectoryTree"));
var _FilePreview = _interopRequireDefault(require("./FilePreview"));
var _style = _interopRequireDefault(require("./style"));
// File content service interface
// Folder properties
// Ref interface type
const ForwardFolder = /*#__PURE__*/_react.default.forwardRef((props, ref) => {
const {
prefixCls: customizePrefixCls,
className,
classNames,
styles,
style,
treeData,
directoryIcons,
previewRender,
directoryTitle,
previewTitle,
selectable = true,
defaultSelectedFile,
defaultExpandAll = true,
selectedFile,
onSelectedFileChange,
directoryTreeWith = 278,
emptyRender,
defaultExpandedPaths,
expandedPaths,
onExpandedPathsChange,
onFileClick,
onFolderClick
} = props;
// ============================= Refs =============================
const containerRef = _react.default.useRef(null);
(0, _useProxyImperativeHandle.default)(ref, () => {
return {
nativeElement: containerRef.current
};
});
// ============================ State ============================
// Find node and validate path
const findNodeAndValidate = (0, _react.useCallback)((path, validateAsFile = false) => {
if (!path) return {
node: undefined,
isValid: false
};
const segments = Array.isArray(path) ? path.filter(Boolean) : path.split('/').filter(Boolean);
if (segments.length === 0) return {
node: undefined,
isValid: false
};
const findNode = (nodes, index = 0) => {
if (index >= segments.length) return undefined;
const currentSegment = segments[index];
for (const node of nodes) {
if (node.path === currentSegment) {
return index === segments.length - 1 ? node : node.children ? findNode(node.children, index + 1) : undefined;
}
}
return undefined;
};
const node = findNode(treeData);
const isValid = validateAsFile ? !!node && (!node?.children || node.children.length === 0) : !!node;
return {
node,
isValid
};
}, [treeData]);
const [validSelectedFile, setValidSelectedFile] = (0, _react.useState)(false);
const isValidSelectedFile = filePath => !!(filePath && filePath.length > 0 && findNodeAndValidate(filePath, true).isValid);
const [expandedPathsState, setExpandedPaths] = (0, _util.useControlledState)(defaultExpandedPaths, expandedPaths);
const [selectedFileState, setSelectedFileState] = (0, _util.useControlledState)(isValidSelectedFile(defaultSelectedFile || []) ? defaultSelectedFile || [] : [], selectedFile);
(0, _react.useEffect)(() => {
const isValid = isValidSelectedFile(selectedFile || defaultSelectedFile || []);
setValidSelectedFile(isValid);
}, [selectedFile, treeData, defaultSelectedFile]);
const [fileContent, setFileContent] = (0, _react.useState)('');
const [loadingContent, setLoadingContent] = (0, _react.useState)(false);
// ============================ Prefix ============================
const {
getPrefixCls,
direction
} = (0, _xProvider.useXProviderContext)();
const prefixCls = getPrefixCls('folder', customizePrefixCls);
const [hashId, cssVarCls] = (0, _style.default)(prefixCls);
const contextConfig = (0, _useXComponentConfig.default)('folder');
const [locale] = (0, _locale.useLocale)('Folder', _en_US.default.Folder);
// ============================ Style ============================
const mergedCls = (0, _clsx.clsx)(prefixCls, contextConfig.className, className, classNames?.root, hashId, cssVarCls, {
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-selectable`]: selectable
});
// ============================ Event Handlers ============================
const handleSelect = (_keys, info) => {
const keys = _keys;
const nodes = Array.isArray(info.selectedNodes) ? info.selectedNodes : [info.selectedNodes];
// Check if a folder was clicked
const isFolder = nodes.some(node => !node.isLeaf);
if (isFolder) {
// Click folder: don't update selectedFileState, only trigger folder click event
if (nodes.length === 1) {
const node = nodes[0];
onFolderClick?.(node.path);
}
return;
}
// Convert full path to array format
const pathArray = keys[0]?.split('/').filter(Boolean) || [];
// Avoid empty or invalid paths
if (pathArray.length === 0) return;
// Get selected file name and content (single file selection)
const selectedNode = nodes[0];
const fileName = selectedNode?.title;
const fileContent = selectedNode?.content;
// Trigger selection change callback (main interaction method)
onSelectedFileChange?.({
path: pathArray,
title: fileName,
content: fileContent
});
// // Update internal state in uncontrolled mode
const isControlled = selectedFile !== undefined;
if (!isControlled) {
setValidSelectedFile(true);
setSelectedFileState(pathArray);
}
// Handle single file click event
if (nodes.length === 1) {
const node = nodes[0];
onFileClick?.(node.path, node.content);
}
};
const handleExpand = keys => {
const newPaths = keys;
setExpandedPaths(newPaths);
onExpandedPathsChange?.(newPaths);
};
// ============================ Effects ============================
(0, _react.useEffect)(() => {
const loadFileContent = async () => {
if (!validSelectedFile || selectedFileState.length === 0) {
setFileContent('');
setLoadingContent(false);
return;
}
const filePath = selectedFileState.join('/');
// First check if the node already has content
const segments = filePath.split('/').filter(segment => segment !== '');
const {
node
} = findNodeAndValidate(segments);
// If file content service is available, use it to load content
if (props.fileContentService) {
setLoadingContent(true);
try {
const content = await props.fileContentService.loadFileContent(filePath);
setFileContent(content);
} catch (error) {
setFileContent(`// ${locale?.loadError}: ${error instanceof Error ? error.message : 'Unknown error'}`);
} finally {
setLoadingContent(false);
}
} else if (node?.content) {
// If node already has content, use it directly
setFileContent(node.content);
setLoadingContent(false);
return;
} else {
// No file content service, show prompt message
setFileContent(`// ${locale.noService}`);
setLoadingContent(false);
}
};
loadFileContent();
}, [validSelectedFile, selectedFileState, treeData, props.fileContentService, findNodeAndValidate]);
// ============================ Style ============================
const mergedStyle = {
...contextConfig.style,
...styles?.root,
...style
};
return /*#__PURE__*/_react.default.createElement("div", {
ref: containerRef,
className: mergedCls,
style: mergedStyle
}, /*#__PURE__*/_react.default.createElement(_antd.Flex, {
className: `${prefixCls}-container`
}, /*#__PURE__*/_react.default.createElement(_antd.Splitter, null, /*#__PURE__*/_react.default.createElement(_antd.Splitter.Panel, {
defaultSize: directoryTreeWith
}, /*#__PURE__*/_react.default.createElement("div", {
className: (0, _clsx.clsx)(`${prefixCls}-directory-tree`, classNames?.directoryTree),
style: {
...contextConfig.styles?.directoryTree,
...styles?.directoryTree
}
}, /*#__PURE__*/_react.default.createElement(_DirectoryTree.default, {
directoryIcons: directoryIcons,
prefixCls: customizePrefixCls,
treeData: treeData,
selectedKeys: selectable && selectedFileState && validSelectedFile ? [selectedFileState.join('/')] : [],
classNames: classNames,
styles: styles,
expandedKeys: expandedPathsState,
onSelect: handleSelect,
onExpand: handleExpand,
defaultExpandAll: defaultExpandAll,
directoryTitle: directoryTitle
}))), /*#__PURE__*/_react.default.createElement(_antd.Splitter.Panel, null, /*#__PURE__*/_react.default.createElement(_FilePreview.default, {
emptyRender: emptyRender,
prefixCls: customizePrefixCls,
classNames: classNames,
styles: styles,
selectedFile: validSelectedFile ? selectedFileState : [],
fileContent: fileContent,
loading: loadingContent,
previewTitle: previewTitle,
previewRender: previewRender,
getFileNode: path => {
if (!path || path.length === 0) return undefined;
const {
node
} = findNodeAndValidate(path);
return node ? {
title: node.title,
path: node.path,
content: node.content
} : undefined;
}
})))));
});
const Folder = ForwardFolder;
if (process.env.NODE_ENV !== 'production') {
Folder.displayName = 'Folder';
}
var _default = exports.default = Folder;