feat: 重构模板管理,采用树状结构存储文件和目录,优化模板创建和编辑流程

This commit is contained in:
zhanggy committed 2025-10-28 18:34:31 +08:00
1 parent f8890ff1e9
commit 5ca31a5348
4 files changed
+349 -244

No files matched your search

+32 -17
View File
@@ -177,25 +177,40 @@ export default class IPCs {
const baseDirPath = join(targetPath, baseDirName) const baseDirPath = join(targetPath, baseDirName)
ensureDirSync(baseDirPath) ensureDirSync(baseDirPath)
// Create directories from template // Helper function to traverse the tree and collect all nodes
for (const dir of template.directories) { const collectAllNodes = (nodes, allNodes = []) => {
const dirPath = join(baseDirPath, dir.path) for (const node of nodes) {
ensureDirSync(dirPath) allNodes.push(node);
} if (node.children) {
collectAllNodes(node.children, allNodes);
}
}
return allNodes;
};
// Create files from template // Collect all nodes from the tree structure
for (const file of template.files) { if (template.treeStructure && template.treeStructure.length > 0) {
const filePath = join(baseDirPath, file.path) const allNodes = collectAllNodes(template.treeStructure);
const fileDir = dirname(filePath)
ensureDirSync(fileDir)
if (file.sourceFilePath) { // Process all nodes, making sure directories are created before files
// Copy from source file for (const node of allNodes) {
const sourceContent = readFileSync(file.sourceFilePath) const nodePath = join(baseDirPath, node.path);
writeFileSync(filePath, sourceContent)
} else if (file.content) { if (node.type === 'directory') {
// Write content directly ensureDirSync(nodePath);
writeFileSync(filePath, file.content, 'utf-8') } else if (node.type === 'file') {
const fileDir = dirname(nodePath);
ensureDirSync(fileDir);
if (node.sourceFilePath) {
// Copy from source file
const sourceContent = readFileSync(node.sourceFilePath);
writeFileSync(nodePath, sourceContent);
} else if (node.content) {
// Write content directly
writeFileSync(nodePath, node.content, 'utf-8');
}
}
} }
} }
} }
+188 -189
View File
@@ -348,8 +348,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue' import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useTemplateStore } from '@/renderer/store/template' import { useTemplateStore, TreeNode } from '@/renderer/store/template'
import { Template } from '@/renderer/store/template'
import TemplateTree from '@/renderer/components/TemplateTree.vue' import TemplateTree from '@/renderer/components/TemplateTree.vue'
import { ConfirmDialogService } from '@/renderer/utils/confirm-dialog-service' import { ConfirmDialogService } from '@/renderer/utils/confirm-dialog-service'
@@ -405,99 +404,37 @@ interface TreeItem {
children?: TreeItem[] children?: TreeItem[]
} }
// Item state // Item state - 使用树状结构
const directories = ref<{ path: string }[]>([]) const treeStructure = ref<TreeNode[]>([])
const files = ref<{ path: string; content?: string; sourceFilePath?: string }[]>([])
// Computed // Computed
const isEditMode = computed(() => !!route.params.id) const isEditMode = computed(() => !!route.params.id)
const treeItems = computed<TreeItem[]>(() => { const treeItems = computed<TreeItem[]>(() => {
// Create root structure // 直接返回树状结构
const root: TreeItem = { return treeStructure.value.map(node => convertToTreeItem(node));
id: 'root', })
name: 'root',
type: 'directory', // 转换 TreeNode 到 TreeItem 的辅助函数
children: [] const convertToTreeItem = (node: TreeNode): TreeItem => {
const treeItem: TreeItem = {
id: node.id,
name: node.name,
type: node.type
};
if (node.children && node.children.length > 0) {
treeItem.children = node.children.map(child => convertToTreeItem(child));
} }
// Process each directory return treeItem;
directories.value.forEach((dir) => { }
const pathParts = dir.path.split('/').filter(part => part !== '') // Split path and remove empty parts
let currentLevel = root.children || []
// Navigate/create the directory structure
for (let i = 0; i < pathParts.length; i++) {
const part = pathParts[i]
const isLastPart = i === pathParts.length - 1
// Check if this part already exists in the current level
let existingItem = currentLevel.find(item => item.name === part)
if (!existingItem) {
// Create new directory item
const newDir: TreeItem = {
id: `dir-${part}-${Date.now()}-${Math.random()}`, // Unique ID
name: part,
type: 'directory',
children: []
}
currentLevel.push(newDir)
existingItem = newDir
}
// Move to the next level if not the last part
if (!isLastPart && existingItem.children) {
currentLevel = existingItem.children
}
}
})
// Process each file and add to the appropriate directory
files.value.forEach((file) => {
const pathParts = file.path.split('/').filter(part => part !== '')
const fileName = pathParts.pop() // Get the last part as filename
if (!fileName) return // Skip if no filename
let currentLevel = root.children || []
// Navigate to the appropriate directory for the file
for (let i = 0; i < pathParts.length; i++) {
const part = pathParts[i]
const dirItem = currentLevel.find(item => item.name === part && item.type === 'directory')
if (dirItem && dirItem.children) {
currentLevel = dirItem.children
} else {
// If the directory doesn't exist, create it
const newDir: TreeItem = {
id: `dir-${part}-${Date.now()}-${Math.random()}`,
name: part,
type: 'directory',
children: []
}
currentLevel.push(newDir)
currentLevel = newDir.children || []
}
}
// Add the file to the current level
currentLevel.push({
id: `file-${fileName}-${Date.now()}-${Math.random()}`,
name: fileName,
type: 'file'
})
})
return root.children || []
})
// Validation rules // Validation rules
const requiredRule = (v: string | null | undefined) => !!v || '此字段为必填项' const requiredRule = (v: string | null | undefined) => !!v || '此字段为必填项'
// Watch for changes to detect unsaved changes // Watch for changes to detect unsaved changes
watch( watch(
[templateName, templateDescription, directories, files], [templateName, templateDescription, treeStructure],
() => { () => {
hasChanges.value = true hasChanges.value = true
}, },
@@ -522,8 +459,7 @@ onMounted(async () => {
if (template) { if (template) {
templateName.value = template.name templateName.value = template.name
templateDescription.value = template.description templateDescription.value = template.description
directories.value = [...template.directories] treeStructure.value = [...template.treeStructure]
files.value = [...template.files]
hasChanges.value = false hasChanges.value = false
} else { } else {
router.push('/templates') router.push('/templates')
@@ -601,29 +537,35 @@ const confirmAddDirectory = () => {
fullPath = parentPath + newDirectoryPath.value fullPath = parentPath + newDirectoryPath.value
} }
const newId = `dir-${Date.now()}-${Math.random()}`
// Check if we're editing an existing directory // Check if we're editing an existing directory
if (currentEditingItem.value && currentEditingItem.value.type === 'directory') { if (currentEditingItem.value && currentEditingItem.value.type === 'directory') {
const index = directories.value.findIndex(d => d.path === currentEditingItem.value?.path) // Find and update the existing directory in the tree
if (index !== -1) { const updatedNode: TreeNode = {
// Update the existing directory path id: currentEditingItem.value.id,
directories.value[index].path = fullPath name: newDirectoryPath.value,
path: fullPath,
// If the directory path changes, update any files/dirs that were inside it type: 'directory'
updatePathsForRenamedDirectory(currentEditingItem.value.path, fullPath) };
} treeStructure.value = updateNodeInTree(treeStructure.value, currentEditingItem.value.path, updatedNode)
} else { } else {
// Check for duplicates only when creating a new directory // Create new directory node
const exists = directories.value.some( const newNode: TreeNode = {
(d) => d.path === fullPath id: newId,
) name: newDirectoryPath.value,
if (exists) { path: fullPath,
showSnackbar('该目录路径已存在!') type: 'directory'
return
} }
directories.value.push({ // Add to tree structure
path: fullPath if (parentDirectoryForNewItems.value) {
}) // Add as child to the parent directory
treeStructure.value = addChildToNode(treeStructure.value, parentDirectoryForNewItems.value, newNode)
} else {
// Add as root node
treeStructure.value.push(newNode)
}
} }
showDirectoryDialog.value = false showDirectoryDialog.value = false
@@ -638,31 +580,44 @@ const confirmAddDirectory = () => {
newDirectoryPath.value = '' newDirectoryPath.value = ''
} }
// Helper function to update paths when a directory is renamed // 在树状结构中更新节点的辅助函数
const updatePathsForRenamedDirectory = (oldPath: string, newPath: string) => { const updateNodeInTree = (nodes: TreeNode[], oldPath: string, updatedNode: TreeNode): TreeNode[] => {
// Update all files that were in the old directory return nodes.map(node => {
files.value = files.value.map(file => { if (node.path === oldPath) {
if (file.path.startsWith(oldPath + '/') || file.path === oldPath) { return updatedNode;
return {
...file,
path: file.path.replace(oldPath, newPath)
}
} }
return file if (node.children) {
})
// Update all directories that were under the old directory
directories.value = directories.value.map(dir => {
if (dir.path.startsWith(oldPath + '/') || dir.path === oldPath) {
return { return {
...dir, ...node,
path: dir.path.replace(oldPath, newPath) children: updateNodeInTree(node.children, oldPath, updatedNode)
} };
} }
return dir return node;
}) });
} }
// 向节点添加子节点的辅助函数
const addChildToNode = (nodes: TreeNode[], parentName: string, childNode: TreeNode): TreeNode[] => {
return nodes.map(node => {
if (node.name === parentName || (node.path && node.path.endsWith('/' + parentName) || node.path === parentName)) {
const children = node.children || [];
return {
...node,
children: [...children, childNode]
};
}
if (node.children) {
return {
...node,
children: addChildToNode(node.children, parentName, childNode)
};
}
return node;
});
}
// Functions to close dialogs and reset state // Functions to close dialogs and reset state
const closeDirectoryDialog = () => { const closeDirectoryDialog = () => {
showDirectoryDialog.value = false showDirectoryDialog.value = false
@@ -693,20 +648,24 @@ const selectAndAddFile = async () => {
fullPath = parentPath + newFilePath.value fullPath = parentPath + newFilePath.value
} }
const newId = `file-${Date.now()}-${Math.random()}`
// Check if we're editing an existing file // Check if we're editing an existing file
if (currentEditingItem.value && currentEditingItem.value.type === 'file') { if (currentEditingItem.value && currentEditingItem.value.type === 'file') {
const index = files.value.findIndex(f => f.path === currentEditingItem.value?.path) // Find and update the existing file in the tree
if (index !== -1) { const updatedNode: TreeNode = {
// Update the existing file id: currentEditingItem.value.id,
files.value[index].path = fullPath name: newFilePath.value,
if (selectedSourcePath.value) { path: fullPath,
files.value[index].sourceFilePath = selectedSourcePath.value type: 'file',
} content: currentEditingItem.value.content,
} sourceFilePath: currentEditingItem.value.sourceFilePath
};
treeStructure.value = updateNodeInTree(treeStructure.value, currentEditingItem.value.path, updatedNode)
} else { } else {
// Check for duplicates only when creating a new file // Check for duplicates only when creating a new file
const exists = files.value.some((f) => f.path === fullPath) const hasDuplicate = checkForDuplicateInTree(treeStructure.value, fullPath);
if (exists) { if (hasDuplicate) {
showSnackbar('该文件路径已存在!') showSnackbar('该文件路径已存在!')
return return
} }
@@ -723,13 +682,24 @@ const selectAndAddFile = async () => {
const fullTargetPath = fullPath ? `${fullPath}/${relativePath}` : relativePath const fullTargetPath = fullPath ? `${fullPath}/${relativePath}` : relativePath
// Check for duplicates // Check for duplicates
const duplicate = files.value.some(f => f.path === fullTargetPath) const hasDuplicateInTree = checkForDuplicateInTree(treeStructure.value, fullTargetPath);
if (!duplicate) { if (!hasDuplicateInTree) {
const newFile = { const newFileNode: TreeNode = {
id: `file-${Date.now()}-${Math.random()}`,
name: fullTargetPath.split('/').pop() || fullTargetPath,
path: fullTargetPath, path: fullTargetPath,
type: 'file',
sourceFilePath: foundFile sourceFilePath: foundFile
} }
files.value.push(newFile)
// Add to tree structure
if (parentDirectoryForNewItems.value) {
// Add as child to the parent directory
treeStructure.value = addChildToNode(treeStructure.value, parentDirectoryForNewItems.value, newFileNode)
} else {
// Add as root node
treeStructure.value.push(newFileNode)
}
} }
} }
@@ -752,12 +722,22 @@ const selectAndAddFile = async () => {
// If we have a selected file, just add it // If we have a selected file, just add it
if (selectedSourcePath.value) { if (selectedSourcePath.value) {
const newFile = { const newFileNode: TreeNode = {
id: newId,
name: newFilePath.value,
path: fullPath, path: fullPath,
type: 'file',
sourceFilePath: selectedSourcePath.value sourceFilePath: selectedSourcePath.value
} }
files.value.push(newFile) // Add to tree structure
if (parentDirectoryForNewItems.value) {
// Add as child to the parent directory
treeStructure.value = addChildToNode(treeStructure.value, parentDirectoryForNewItems.value, newFileNode)
} else {
// Add as root node
treeStructure.value.push(newFileNode)
}
// Reset selection state // Reset selection state
selectedSourcePath.value = '' selectedSourcePath.value = ''
@@ -797,6 +777,21 @@ const selectAndAddFile = async () => {
newFilePath.value = '' newFilePath.value = ''
} }
// 检查树中是否有重复路径的辅助函数
const checkForDuplicateInTree = (nodes: TreeNode[], pathToCheck: string): boolean => {
for (const node of nodes) {
if (node.path === pathToCheck) {
return true;
}
if (node.children) {
if (checkForDuplicateInTree(node.children, pathToCheck)) {
return true;
}
}
}
return false;
}
const goBack = async () => { const goBack = async () => {
if (hasChanges.value) { if (hasChanges.value) {
const confirmed = await ConfirmDialogService.confirm({ const confirmed = await ConfirmDialogService.confirm({
@@ -843,47 +838,56 @@ const editFile = (item: TreeItem) => {
// Unified method to edit items (both files and directories) // Unified method to edit items (both files and directories)
// This function needs to find the actual path of the item in our hierarchical tree // This function needs to find the actual path of the item in our hierarchical tree
const editItem = (item: TreeItem) => { const editItem = (item: TreeItem) => {
if (item.type === 'directory') { // Find the node in the tree structure
// Find the directory path in our directories array const node = findNodeInTree(treeStructure.value, item.id);
// We need to find a directory that ends with the same name as this item if (node) {
const dir = directories.value.find(d => { const nodeData = {
// If the directory path ends with the item name, or the directory path is the same as item name id: node.id,
return d.path === item.name || d.path.endsWith('/' + item.name) || d.path.endsWith('\\' + item.name) path: node.path,
}) type: node.type,
if (dir) { content: node.content, // For files
// Show a dialog to edit the directory path sourceFilePath: node.sourceFilePath, // For files
showEditDirectoryDialog(dir) name: node.name,
};
if (node.type === 'directory') {
showEditDirectoryDialog(nodeData)
} else { } else {
showSnackbar('无法找到对应的目录', 'error') showEditFileDialog(nodeData)
}
} else if (item.type === 'file') {
// Find the file in our files array
const file = files.value.find(f => {
// If the file path ends with the item name, or the file path is the same as item name
return f.path === item.name || f.path.endsWith('/' + item.name) || f.path.endsWith('\\' + item.name)
})
if (file) {
// Show a dialog to edit the file path
showEditFileDialog(file)
} else {
showSnackbar('无法找到对应的文件', 'error')
} }
} else {
showSnackbar('无法找到对应的节点', 'error')
} }
} }
// 在树状结构中查找节点的辅助函数
const findNodeInTree = (nodes: TreeNode[], nodeId: string): TreeNode | null => {
for (const node of nodes) {
if (node.id === nodeId) {
return node;
}
if (node.children) {
const found = findNodeInTree(node.children, nodeId);
if (found) {
return found;
}
}
}
return null;
}
// Show dialog to edit directory // Show dialog to edit directory
const showEditDirectoryDialog = (directory: { path: string }) => { const showEditDirectoryDialog = (directory: TreeNode) => {
// Set up for editing an existing directory // Set up for editing an existing directory
currentEditingItem.value = { ...directory, type: 'directory' } currentEditingItem.value = directory
newDirectoryPath.value = directory.path // Set the current path for editing newDirectoryPath.value = directory.path // Set the current path for editing
showDirectoryDialog.value = true showDirectoryDialog.value = true
autoFocusDirectoryInput() autoFocusDirectoryInput()
} }
// Show dialog to edit file // Show dialog to edit file
const showEditFileDialog = (file: { path: string; content?: string; sourceFilePath?: string }) => { const showEditFileDialog = (file: TreeNode) => {
// Set up for editing an existing file // Set up for editing an existing file
currentEditingItem.value = { ...file, type: 'file' } currentEditingItem.value = file
newFilePath.value = file.path // Set the current path for editing newFilePath.value = file.path // Set the current path for editing
// If the file has a source file path, set it in the selection state // If the file has a source file path, set it in the selection state
@@ -897,7 +901,7 @@ const showEditFileDialog = (file: { path: string; content?: string; sourceFilePa
} }
// Variable to track the current item being edited // Variable to track the current item being edited
const currentEditingItem = ref<{ path: string; type: 'directory' | 'file'; content?: string; sourceFilePath?: string } | null>(null) const currentEditingItem = ref<TreeNode | null>(null)
const deleteItem = async (item: TreeItem) => { const deleteItem = async (item: TreeItem) => {
const confirmed = await ConfirmDialogService.confirm({ const confirmed = await ConfirmDialogService.confirm({
@@ -910,30 +914,26 @@ const deleteItem = async (item: TreeItem) => {
if (!confirmed) return if (!confirmed) return
if (item.type === 'directory') { // 从树状结构中删除项目
// Find all directories that start with the item name (including the exact match) treeStructure.value = removeItemFromTree(treeStructure.value, item.id);
// This matches the directory itself and all its subdirectories
directories.value = directories.value.filter(d => {
// Exclude if exact match or it's a subdirectory
return !isSubPath(d.path, item.name);
});
// Also remove files that are inside this directory
files.value = files.value.filter(f => {
// Exclude if it's inside the directory being deleted
return !isSubPath(f.path, item.name);
});
} else {
// For files, just remove the file
files.value = files.value.filter(f => f.path !== item.name);
}
} }
// Helper function to check if pathA is a subpath of pathB // 从树状结构中删除项目的辅助函数
const isSubPath = (pathA: string, pathB: string) => { const removeItemFromTree = (nodes: TreeNode[], itemId: string): TreeNode[] => {
return pathA === pathB || pathA.startsWith(pathB + '/') || pathA.startsWith(pathB + '\\'); return nodes.filter(node => node.id !== itemId) // 过滤掉要删除的节点
.map(node => {
if (node.children) {
return {
...node,
children: removeItemFromTree(node.children, itemId)
};
}
return node;
});
} }
// Show snackbar message // Show snackbar message
const showSnackbar = (message: string, color: string = 'error') => { const showSnackbar = (message: string, color: string = 'error') => {
snackbarMessage.value = message snackbarMessage.value = message
@@ -979,8 +979,7 @@ const saveTemplate = () => {
const templateData = { const templateData = {
name: templateName.value, name: templateName.value,
description: templateDescription.value, description: templateDescription.value,
files: files.value, treeStructure: treeStructure.value
directories: directories.value
} }
if (isEditMode.value) { if (isEditMode.value) {
+26 -34
View File
@@ -37,10 +37,10 @@
<v-card-text class="flex-grow-1 py-2"> <v-card-text class="flex-grow-1 py-2">
<v-chip-group column> <v-chip-group column>
<v-chip size="small" color="primary" variant="tonal"> <v-chip size="small" color="primary" variant="tonal">
文件: {{ template.files.length }} 节点: {{ countTreeNodes(template.treeStructure).files }}
</v-chip> </v-chip>
<v-chip size="small" color="success" variant="tonal"> <v-chip size="small" color="success" variant="tonal">
目录: {{ template.directories.length }} 目录: {{ countTreeNodes(template.treeStructure).directories }}
</v-chip> </v-chip>
</v-chip-group> </v-chip-group>
</v-card-text> </v-card-text>
@@ -138,7 +138,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useTemplateStore } from '@/renderer/store/template' import { useTemplateStore, TreeNode } from '@/renderer/store/template'
import { Template } from '@/renderer/store/template' import { Template } from '@/renderer/store/template'
import TemplateTree from '@/renderer/components/TemplateTree.vue' import TemplateTree from '@/renderer/components/TemplateTree.vue'
import { ConfirmDialogService } from '@/renderer/utils/confirm-dialog-service' import { ConfirmDialogService } from '@/renderer/utils/confirm-dialog-service'
@@ -166,42 +166,34 @@ const executePath = ref('')
const baseDirNames = ref('') const baseDirNames = ref('')
const executeFormValid = ref(false) const executeFormValid = ref(false)
// Define item type for the tree view // Count files and directories in tree structure
interface TreeNode { const countTreeNodes = (nodes: TreeNode[]) => {
id: string let files = 0;
name: string let directories = 0;
type: 'directory' | 'file'
children?: TreeNode[] const count = (nodeList: TreeNode[]) => {
nodeList.forEach(node => {
if (node.type === 'file') {
files++;
} else if (node.type === 'directory') {
directories++;
if (node.children) {
count(node.children);
}
}
});
};
count(nodes || []);
return { files, directories };
} }
const templateTree = computed<TreeNode[]>(() => { const templateTree = computed<TreeNode[]>(() => {
if (!selectedTemplate.value) return [] if (!selectedTemplate.value) return []
const items: TreeNode[] = [] // 直接返回模板的树状结构
return selectedTemplate.value.treeStructure || []
// Add directories
selectedTemplate.value.directories.forEach((dir) => {
items.push({
id: `dir-${dir.path}`,
name: dir.path.split('/').pop() || dir.path, // Show just the directory name, not full path
type: 'directory',
children: []
})
})
// Add files
selectedTemplate.value.files.forEach((file) => {
items.push({
id: `file-${file.path}`,
name: file.path.split('/').pop() || file.path, // Show just the file name, not full path
type: 'file'
})
})
// Auto-expand all nodes when showing template details
expandedTreeNodes.value = items.map(item => item.id)
return items
}) })
const navigateToCreateTemplate = () => { const navigateToCreateTemplate = () => {
+103 -4
View File
@@ -1,22 +1,35 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref } from 'vue' import { ref } from 'vue'
interface TemplateFile { // 保持旧的接口用于数据迁移
interface OldTemplateFile {
path: string path: string
content?: string // Content for text files that are created directly content?: string // Content for text files that are created directly
sourceFilePath?: string // Path to copy from for file copying sourceFilePath?: string // Path to copy from for file copying
} }
interface TemplateDirectory { interface OldTemplateDirectory {
path: string path: string
} }
// 定义树状结构的节点
export interface TreeNode {
id: string
name: string
path: string // 完整路径
type: 'directory' | 'file'
children?: TreeNode[]
// 文件特定属性
content?: string
sourceFilePath?: string
}
export interface Template { export interface Template {
id: string id: string
name: string name: string
description: string description: string
files: TemplateFile[] // 使用树状结构存储
directories: TemplateDirectory[] treeStructure: TreeNode[]
} }
export const useTemplateStore = defineStore('template', () => { export const useTemplateStore = defineStore('template', () => {
@@ -95,6 +108,92 @@ export const useTemplateStore = defineStore('template', () => {
})) }))
} }
// 将扁平结构转换为树状结构
function convertFlatToTreeStructure(files: OldTemplateFile[], directories: OldTemplateDirectory[]): TreeNode[] {
// 创建根节点
const root: TreeNode = {
id: 'root',
name: 'root',
path: '',
type: 'directory',
children: []
}
// 先处理所有目录
directories.forEach(dir => {
const pathParts = dir.path.split('/').filter(part => part !== '') // Split path and remove empty parts
let currentLevel = root.children || []
// Navigate/create the directory structure
for (let i = 0; i < pathParts.length; i++) {
const part = pathParts[i]
const isLastPart = i === pathParts.length - 1
// Check if this part already exists in the current level
let existingItem = currentLevel.find(item => item.name === part)
if (!existingItem) {
// Create new directory item
const newDir: TreeNode = {
id: `dir-${part}-${Date.now()}-${Math.random()}`, // Unique ID
name: part,
path: dir.path.substring(0, dir.path.indexOf(part) + part.length), // Full path up to this part
type: 'directory',
children: []
}
currentLevel.push(newDir)
existingItem = newDir
}
// Move to the next level if not the last part
if (!isLastPart && existingItem.children) {
currentLevel = existingItem.children
}
}
})
// Process each file and add to the appropriate directory
files.forEach(file => {
const pathParts = file.path.split('/').filter(part => part !== '')
const fileName = pathParts.pop() // Get the last part as filename
if (!fileName) return // Skip if no filename
let currentLevel = root.children || []
// Navigate to the appropriate directory for the file
for (let i = 0; i < pathParts.length; i++) {
const part = pathParts[i]
const dirItem = currentLevel.find(item => item.name === part && item.type === 'directory')
if (dirItem && dirItem.children) {
currentLevel = dirItem.children
} else {
// If the directory doesn't exist, create it
const newDir: TreeNode = {
id: `dir-${part}-${Date.now()}-${Math.random()}`,
name: part,
path: pathParts.slice(0, i+1).join('/'),
type: 'directory',
children: []
}
currentLevel.push(newDir)
currentLevel = newDir.children || []
}
}
// Add the file to the current level
currentLevel.push({
id: `file-${fileName}-${Date.now()}-${Math.random()}`,
name: fileName,
path: file.path,
type: 'file'
})
})
return root.children || []
}
return { return {
templates, templates,
loadTemplates, loadTemplates,