diff --git a/apps/studio/electron/main/code/components.ts b/apps/studio/electron/main/code/components.ts index 0d67ad2ef6..58e4f423bd 100644 --- a/apps/studio/electron/main/code/components.ts +++ b/apps/studio/electron/main/code/components.ts @@ -17,6 +17,7 @@ function isUppercase(s: string) { export interface ReactComponentDescriptor { name: string; sourceFilePath: string; + isDelete?: boolean; } function isExported(node: FunctionDeclaration | VariableStatement | ClassDeclaration) { @@ -42,7 +43,7 @@ function getReactComponentDescriptor( } const initializer = declaration.getInitializer(); - if (Node.isArrowFunction(initializer)) { + if (Node.isArrowFunction(initializer) || Node.isFunctionExpression(initializer)) { return { name }; } } @@ -99,6 +100,7 @@ function extractReactComponentsFromFile(filePath: string) { exportedComponents.push({ ...descriptor, sourceFilePath: sourceFile.getFilePath(), + isDelete: false, }); } }); @@ -139,5 +141,191 @@ export async function extractComponentsFromDirectory(dir: string) { allExportedComponents.push(...components); }); + const updatedExportedComponent = checkIfComponentIsUsed(allExportedComponents, files); + + const filteredComponents = updatedExportedComponent.filter((component) => { + const fileName = path.basename(component.sourceFilePath).toLowerCase(); + return !(fileName === 'page.tsx' || fileName === 'layout.tsx'); + }); + + return filteredComponents; +} + +export async function duplicateComponent(filePath: string, componentName: string) { + try { + const directory = path.dirname(filePath); + + const project = new Project(); + const sourceFile = project.addSourceFileAtPath(filePath); + + const baseName = componentName.replace(/\d+$/, ''); + const files = await fs.readdir(directory); + + const regex = new RegExp(`^${baseName}(\\d+)?.tsx$`); + + const existingNumbers = files + .map((file) => { + const match = file.match(regex); + return match && match[1] ? parseInt(match[1], 10) : 0; + }) + .filter((num) => num !== null) + .sort((a, b) => a - b); + + const nextNumber = existingNumbers.length ? Math.max(...existingNumbers) + 1 : 1; + const newComponentName = `${baseName}${nextNumber}`; + + const newFileName = `${newComponentName}.tsx`; + const newFilePath = path.join(directory, newFileName); + + const clonedSourceFile = sourceFile.copy(newFilePath); + + const nodesToRename = [ + ...clonedSourceFile.getFunctions(), + ...clonedSourceFile.getVariableDeclarations(), + ...clonedSourceFile.getClasses(), + ]; + + nodesToRename.forEach((node) => { + if (node.getName() === componentName) { + node.rename(newComponentName); + } + }); + + await clonedSourceFile.save(); + + return newFilePath; + } catch (error) { + console.error('Error duplicating component:', error); + throw error; + } +} + +export async function renameComponent(newName: string, filePath: string) { + try { + const directory = path.dirname(filePath); + const oldFileName = path.basename(filePath, '.tsx'); + const newFilePath = path.join(directory, `${newName}.tsx`); + + const project = new Project(); + const sourceFile = project.addSourceFileAtPath(filePath); + + const nodesToRename = [ + ...sourceFile.getFunctions(), + ...sourceFile.getVariableDeclarations(), + ...sourceFile.getClasses(), + ]; + + let renamed = false; + + nodesToRename.forEach((node) => { + if (node.getName() === oldFileName) { + node.rename(newName); + renamed = true; + } + }); + + if (!renamed) { + console.warn(`No matching component named '${oldFileName}' found for renaming.`); + } + + await sourceFile.save(); + await fs.rename(filePath, newFilePath); + + return newFilePath; + } catch (error) { + console.error('Error renaming component:', error); + throw error; + } +} + +export async function createNewComponent(componentName: string, filePath: string) { + try { + const dirPath = path.dirname(filePath); + + const newFilePath = path.join(dirPath, `${componentName}.tsx`); + + const componentTemplate = `export default function ${componentName}() { + return ( +