mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 08:53:36 +00:00
feat: implement upsert project functionality in sidebar and welcome view
- Refactored project handling in Sidebar and WelcomeView components to use a new `upsertAndSetCurrentProject` action for creating or updating projects. - Enhanced theme preservation logic during project creation and updates by integrating theme management directly into the store action. - Cleaned up redundant code related to project existence checks and state updates, improving maintainability and readability.
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useMemo, useEffect, useCallback, useRef } from "react";
|
import { useState, useMemo, useEffect, useCallback, useRef } from "react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useAppStore, formatShortcut } from "@/store/app-store";
|
import { useAppStore, formatShortcut, type ThemeMode } from "@/store/app-store";
|
||||||
import { CoursePromoBadge } from "@/components/ui/course-promo-badge";
|
import { CoursePromoBadge } from "@/components/ui/course-promo-badge";
|
||||||
import { IS_MARKETING } from "@/config/app-config";
|
import { IS_MARKETING } from "@/config/app-config";
|
||||||
import {
|
import {
|
||||||
@@ -188,7 +188,7 @@ export function Sidebar() {
|
|||||||
currentView,
|
currentView,
|
||||||
sidebarOpen,
|
sidebarOpen,
|
||||||
projectHistory,
|
projectHistory,
|
||||||
addProject,
|
upsertAndSetCurrentProject,
|
||||||
setCurrentProject,
|
setCurrentProject,
|
||||||
setCurrentView,
|
setCurrentView,
|
||||||
toggleSidebar,
|
toggleSidebar,
|
||||||
@@ -473,39 +473,14 @@ export function Sidebar() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if project already exists (by path) to preserve theme and other settings
|
// Upsert project and set as current (handles both create and update cases)
|
||||||
const existingProject = projects.find((p) => p.path === path);
|
// Theme preservation is handled by the store action
|
||||||
|
const trashedProject = trashedProjects.find((p) => p.path === path);
|
||||||
let project: Project;
|
const effectiveTheme =
|
||||||
if (existingProject) {
|
(trashedProject?.theme as ThemeMode | undefined) ||
|
||||||
// Update existing project, preserving theme and other properties
|
(currentProject?.theme as ThemeMode | undefined) ||
|
||||||
project = {
|
globalTheme;
|
||||||
...existingProject,
|
const project = upsertAndSetCurrentProject(path, name, effectiveTheme);
|
||||||
name, // Update name in case it changed
|
|
||||||
lastOpened: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
// Update the project in the store (this will update the existing entry)
|
|
||||||
const updatedProjects = projects.map((p) =>
|
|
||||||
p.id === existingProject.id ? project : p
|
|
||||||
);
|
|
||||||
useAppStore.setState({ projects: updatedProjects });
|
|
||||||
} else {
|
|
||||||
// Create new project - check for trashed project with same path first (preserves theme if deleted/recreated)
|
|
||||||
// Then fall back to current effective theme, then global theme
|
|
||||||
const trashedProject = trashedProjects.find((p) => p.path === path);
|
|
||||||
const effectiveTheme =
|
|
||||||
trashedProject?.theme || currentProject?.theme || globalTheme;
|
|
||||||
project = {
|
|
||||||
id: `project-${Date.now()}`,
|
|
||||||
name,
|
|
||||||
path,
|
|
||||||
lastOpened: new Date().toISOString(),
|
|
||||||
theme: effectiveTheme,
|
|
||||||
};
|
|
||||||
addProject(project);
|
|
||||||
}
|
|
||||||
|
|
||||||
setCurrentProject(project);
|
|
||||||
|
|
||||||
// Check if app_spec.txt exists
|
// Check if app_spec.txt exists
|
||||||
const specExists = await hasAppSpec(path);
|
const specExists = await hasAppSpec(path);
|
||||||
@@ -540,10 +515,8 @@ export function Sidebar() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
projects,
|
|
||||||
trashedProjects,
|
trashedProjects,
|
||||||
addProject,
|
upsertAndSetCurrentProject,
|
||||||
setCurrentProject,
|
|
||||||
currentProject,
|
currentProject,
|
||||||
globalTheme,
|
globalTheme,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { useAppStore } from "@/store/app-store";
|
import { useAppStore, type ThemeMode } from "@/store/app-store";
|
||||||
import { getElectronAPI, type Project } from "@/lib/electron";
|
import { getElectronAPI, type Project } from "@/lib/electron";
|
||||||
import { initializeProject } from "@/lib/project-init";
|
import { initializeProject } from "@/lib/project-init";
|
||||||
import {
|
import {
|
||||||
@@ -36,8 +36,14 @@ import { getHttpApiClient } from "@/lib/http-api-client";
|
|||||||
import type { StarterTemplate } from "@/lib/templates";
|
import type { StarterTemplate } from "@/lib/templates";
|
||||||
|
|
||||||
export function WelcomeView() {
|
export function WelcomeView() {
|
||||||
const { projects, addProject, setCurrentProject, setCurrentView } =
|
const {
|
||||||
useAppStore();
|
projects,
|
||||||
|
trashedProjects,
|
||||||
|
currentProject,
|
||||||
|
upsertAndSetCurrentProject,
|
||||||
|
setCurrentView,
|
||||||
|
theme: globalTheme,
|
||||||
|
} = useAppStore();
|
||||||
const [showNewProjectModal, setShowNewProjectModal] = useState(false);
|
const [showNewProjectModal, setShowNewProjectModal] = useState(false);
|
||||||
const [isCreating, setIsCreating] = useState(false);
|
const [isCreating, setIsCreating] = useState(false);
|
||||||
const [isOpening, setIsOpening] = useState(false);
|
const [isOpening, setIsOpening] = useState(false);
|
||||||
@@ -98,35 +104,14 @@ export function WelcomeView() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if project already exists (by path) to preserve theme and other settings
|
// Upsert project and set as current (handles both create and update cases)
|
||||||
const existingProject = projects.find((p) => p.path === path);
|
// Theme preservation is handled by the store action
|
||||||
|
const trashedProject = trashedProjects.find((p) => p.path === path);
|
||||||
let project: Project;
|
const effectiveTheme =
|
||||||
if (existingProject) {
|
(trashedProject?.theme as ThemeMode | undefined) ||
|
||||||
// Update existing project, preserving theme and other properties
|
(currentProject?.theme as ThemeMode | undefined) ||
|
||||||
project = {
|
globalTheme;
|
||||||
...existingProject,
|
const project = upsertAndSetCurrentProject(path, name, effectiveTheme);
|
||||||
name, // Update name in case it changed
|
|
||||||
lastOpened: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
// Update the project in the store (this will update the existing entry)
|
|
||||||
const updatedProjects = projects.map((p) =>
|
|
||||||
p.id === existingProject.id ? project : p
|
|
||||||
);
|
|
||||||
// We need to manually update projects since addProject would create a duplicate
|
|
||||||
useAppStore.setState({ projects: updatedProjects });
|
|
||||||
} else {
|
|
||||||
// Create new project
|
|
||||||
project = {
|
|
||||||
id: `project-${Date.now()}`,
|
|
||||||
name,
|
|
||||||
path,
|
|
||||||
lastOpened: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
addProject(project);
|
|
||||||
}
|
|
||||||
|
|
||||||
setCurrentProject(project);
|
|
||||||
|
|
||||||
// Show initialization dialog if files were created
|
// Show initialization dialog if files were created
|
||||||
if (initResult.createdFiles && initResult.createdFiles.length > 0) {
|
if (initResult.createdFiles && initResult.createdFiles.length > 0) {
|
||||||
@@ -161,7 +146,13 @@ export function WelcomeView() {
|
|||||||
setIsOpening(false);
|
setIsOpening(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[projects, addProject, setCurrentProject, analyzeProject]
|
[
|
||||||
|
trashedProjects,
|
||||||
|
currentProject,
|
||||||
|
globalTheme,
|
||||||
|
upsertAndSetCurrentProject,
|
||||||
|
analyzeProject,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleOpenProject = useCallback(async () => {
|
const handleOpenProject = useCallback(async () => {
|
||||||
|
|||||||
@@ -417,6 +417,11 @@ export interface AppActions {
|
|||||||
deleteTrashedProject: (projectId: string) => void;
|
deleteTrashedProject: (projectId: string) => void;
|
||||||
emptyTrash: () => void;
|
emptyTrash: () => void;
|
||||||
setCurrentProject: (project: Project | null) => void;
|
setCurrentProject: (project: Project | null) => void;
|
||||||
|
upsertAndSetCurrentProject: (
|
||||||
|
path: string,
|
||||||
|
name: string,
|
||||||
|
theme?: ThemeMode
|
||||||
|
) => Project; // Upsert project by path and set as current
|
||||||
reorderProjects: (oldIndex: number, newIndex: number) => void;
|
reorderProjects: (oldIndex: number, newIndex: number) => void;
|
||||||
cyclePrevProject: () => void; // Cycle back through project history (Q)
|
cyclePrevProject: () => void; // Cycle back through project history (Q)
|
||||||
cycleNextProject: () => void; // Cycle forward through project history (E)
|
cycleNextProject: () => void; // Cycle forward through project history (E)
|
||||||
@@ -779,6 +784,58 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
upsertAndSetCurrentProject: (path, name, theme) => {
|
||||||
|
const {
|
||||||
|
projects,
|
||||||
|
trashedProjects,
|
||||||
|
currentProject,
|
||||||
|
theme: globalTheme,
|
||||||
|
} = get();
|
||||||
|
const existingProject = projects.find((p) => p.path === path);
|
||||||
|
let project: Project;
|
||||||
|
|
||||||
|
if (existingProject) {
|
||||||
|
// Update existing project, preserving theme and other properties
|
||||||
|
project = {
|
||||||
|
...existingProject,
|
||||||
|
name, // Update name in case it changed
|
||||||
|
lastOpened: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
// Update the project in the store
|
||||||
|
const updatedProjects = projects.map((p) =>
|
||||||
|
p.id === existingProject.id ? project : p
|
||||||
|
);
|
||||||
|
set({ projects: updatedProjects });
|
||||||
|
} else {
|
||||||
|
// Create new project - check for trashed project with same path first (preserves theme if deleted/recreated)
|
||||||
|
// Then fall back to provided theme, then current project theme, then global theme
|
||||||
|
const trashedProject = trashedProjects.find((p) => p.path === path);
|
||||||
|
const effectiveTheme =
|
||||||
|
theme ||
|
||||||
|
trashedProject?.theme ||
|
||||||
|
currentProject?.theme ||
|
||||||
|
globalTheme;
|
||||||
|
project = {
|
||||||
|
id: `project-${Date.now()}`,
|
||||||
|
name,
|
||||||
|
path,
|
||||||
|
lastOpened: new Date().toISOString(),
|
||||||
|
theme: effectiveTheme,
|
||||||
|
};
|
||||||
|
// Add the new project to the store
|
||||||
|
set({
|
||||||
|
projects: [
|
||||||
|
...projects,
|
||||||
|
{ ...project, lastOpened: new Date().toISOString() },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set as current project (this will also update history and view)
|
||||||
|
get().setCurrentProject(project);
|
||||||
|
return project;
|
||||||
|
},
|
||||||
|
|
||||||
cyclePrevProject: () => {
|
cyclePrevProject: () => {
|
||||||
const { projectHistory, projectHistoryIndex, projects } = get();
|
const { projectHistory, projectHistoryIndex, projects } = get();
|
||||||
|
|
||||||
@@ -1241,7 +1298,9 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
const current = get().lastSelectedSessionByProject;
|
const current = get().lastSelectedSessionByProject;
|
||||||
if (sessionId === null) {
|
if (sessionId === null) {
|
||||||
// Remove the entry for this project
|
// Remove the entry for this project
|
||||||
const { [projectPath]: _, ...rest } = current;
|
const rest = Object.fromEntries(
|
||||||
|
Object.entries(current).filter(([key]) => key !== projectPath)
|
||||||
|
);
|
||||||
set({ lastSelectedSessionByProject: rest });
|
set({ lastSelectedSessionByProject: rest });
|
||||||
} else {
|
} else {
|
||||||
set({
|
set({
|
||||||
|
|||||||
@@ -55,6 +55,16 @@ export function createTemplatesRoutes(): Router {
|
|||||||
// Build full project path
|
// Build full project path
|
||||||
const projectPath = path.join(parentDir, sanitizedName);
|
const projectPath = path.join(parentDir, sanitizedName);
|
||||||
|
|
||||||
|
const resolvedParent = path.resolve(parentDir);
|
||||||
|
const resolvedProject = path.resolve(projectPath);
|
||||||
|
const relativePath = path.relative(resolvedParent, resolvedProject);
|
||||||
|
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
error: "Invalid project name; potential path traversal attempt.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Check if directory already exists
|
// Check if directory already exists
|
||||||
try {
|
try {
|
||||||
await fs.access(projectPath);
|
await fs.access(projectPath);
|
||||||
|
|||||||
Reference in New Issue
Block a user