Skip to content

Commit f5b677b

Browse files
committed
add branch route
1 parent d51ebdb commit f5b677b

File tree

6 files changed

+127
-22
lines changed

6 files changed

+127
-22
lines changed

apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const useStartProject = () => {
2727
const apiUtils = api.useUtils();
2828
const { data: user, isLoading: isUserLoading, error: userError } = api.user.get.useQuery();
2929
const { data: project, isLoading: isProjectLoading, error: projectError } = api.project.get.useQuery({ projectId: editorEngine.projectId });
30+
const { data: branch, isLoading: isBranchLoading, error: branchError } = api.branch.get.useQuery({ projectId: editorEngine.projectId });
3031
const { data: canvasWithFrames, isLoading: isCanvasLoading, error: canvasError } = api.userCanvas.getWithFrames.useQuery({ projectId: editorEngine.projectId });
3132
const { data: conversations, isLoading: isConversationsLoading, error: conversationsError } = api.chat.conversation.getAll.useQuery({ projectId: editorEngine.projectId });
3233
const { data: creationRequest, isLoading: isCreationRequestLoading, error: creationRequestError } = api.project.createRequest.getPendingRequest.useQuery({ projectId: editorEngine.projectId });
@@ -129,10 +130,11 @@ export const useStartProject = () => {
129130
!isCanvasLoading &&
130131
!isConversationsLoading &&
131132
!isCreationRequestLoading &&
132-
!isSandboxLoading;
133+
!isSandboxLoading &&
134+
!isBranchLoading;
133135

134136
setIsProjectReady(allQueriesResolved);
135-
}, [isUserLoading, isProjectLoading, isCanvasLoading, isConversationsLoading, isCreationRequestLoading, isSandboxLoading]);
137+
}, [isUserLoading, isProjectLoading, isCanvasLoading, isConversationsLoading, isCreationRequestLoading, isSandboxLoading, isBranchLoading]);
136138

137139
useEffect(() => {
138140
setError(userError?.message ?? projectError?.message ?? canvasError?.message ?? conversationsError?.message ?? creationRequestError?.message ?? null);

apps/web/client/src/components/store/editor/branch/manager.ts

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,36 +17,42 @@ export class BranchManager {
1717
return this.currentBranchId;
1818
}
1919

20-
getCurrentSandbox(): SandboxManager | null {
20+
getCurrentSandbox(): SandboxManager {
2121
if (!this.currentBranchId) {
22-
throw new Error('No branch selected. Please select a branch first.');
22+
throw new Error('No branch selected. Call switchToBranch() first.');
2323
}
2424

2525
if (!this.branchIdToSandboxManager.has(this.currentBranchId)) {
26-
// Create new SandboxManager for this branch
2726
const sandboxManager = new SandboxManager(this.editorEngine);
2827
this.branchIdToSandboxManager.set(this.currentBranchId, sandboxManager);
2928
}
3029

31-
return this.branchIdToSandboxManager.get(this.currentBranchId) ?? null;
30+
return this.branchIdToSandboxManager.get(this.currentBranchId)!;
31+
}
32+
33+
async startCurrentBranchSandbox(): Promise<void> {
34+
if (!this.currentBranchId) {
35+
throw new Error('No branch selected. Call switchToBranch() first.');
36+
}
37+
38+
const branch = await this.getBranchById(this.currentBranchId);
39+
await this.getCurrentSandbox().session.start(branch.sandbox.id);
3240
}
3341

3442
async switchToBranch(branchId: string): Promise<void> {
3543
if (this.currentBranchId === branchId) {
36-
return; // Already on this branch
44+
return;
3745
}
3846

39-
// TODO: Validate branch exists in database
4047
this.currentBranchId = branchId;
4148
}
4249

4350
async createBranch(
4451
name: string,
4552
description?: string,
46-
fromBranchId?: string
53+
fromBranchId?: string,
54+
isDefault = false
4755
): Promise<Branch> {
48-
// TODO: Implement database branch creation
49-
// For now, return a mock branch
5056
const newBranch: Branch = {
5157
id: `branch-${Date.now()}`,
5258
name,
@@ -55,16 +61,10 @@ export class BranchManager {
5561
updatedAt: new Date(),
5662
git: null,
5763
sandbox: {
58-
// Mock
5964
id: `sandbox-${Date.now()}`,
6065
},
6166
};
6267

63-
// If copying from another branch, we could copy the sandbox state here
64-
if (fromBranchId && this.branchIdToSandboxManager.has(fromBranchId)) {
65-
// TODO: Implement sandbox state copying
66-
}
67-
6868
return newBranch;
6969
}
7070

@@ -73,22 +73,61 @@ export class BranchManager {
7373
throw new Error('Cannot delete the currently active branch');
7474
}
7575

76-
// Clean up sandbox manager
7776
const sandboxManager = this.branchIdToSandboxManager.get(branchId);
7877
if (sandboxManager) {
7978
sandboxManager.clear();
8079
this.branchIdToSandboxManager.delete(branchId);
8180
}
81+
}
82+
83+
async getDefaultBranch(): Promise<Branch> {
84+
return {
85+
id: 'main-branch-id',
86+
name: 'main',
87+
description: 'Default main branch',
88+
createdAt: new Date(),
89+
updatedAt: new Date(),
90+
git: null,
91+
sandbox: {
92+
id: 'main-sandbox-id',
93+
},
94+
};
95+
}
8296

83-
// TODO: Delete branch from database
97+
async getBranchById(branchId: string): Promise<Branch> {
98+
return {
99+
id: branchId,
100+
name: branchId === 'main-branch-id' ? 'main' : `branch-${branchId}`,
101+
description: null,
102+
createdAt: new Date(),
103+
updatedAt: new Date(),
104+
git: null,
105+
sandbox: {
106+
id: `${branchId}-sandbox`,
107+
},
108+
};
109+
}
110+
111+
112+
private async createMainBranch(): Promise<Branch> {
113+
return {
114+
id: 'main-branch-id',
115+
name: 'main',
116+
description: 'Default main branch',
117+
createdAt: new Date(),
118+
updatedAt: new Date(),
119+
git: null,
120+
sandbox: {
121+
id: 'main-sandbox-id',
122+
},
123+
};
84124
}
85125

86126
async listBranches(): Promise<Branch[]> {
87127
return [];
88128
}
89129

90130
clear(): void {
91-
// Clear all sandbox managers
92131
for (const sandboxManager of this.branchIdToSandboxManager.values()) {
93132
sandboxManager.clear();
94133
}

apps/web/client/src/components/store/editor/engine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export class EditorEngine {
6666

6767
readonly branches: BranchManager = new BranchManager(this);
6868
// Sandbox getter - returns branch-specific sandbox
69-
get sandbox(): SandboxManager | null {
69+
get sandbox(): SandboxManager {
7070
return this.branches.getCurrentSandbox();
7171
}
7272

apps/web/client/src/server/api/root.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const appRouter = createTRPCRouter({
2828
user: userRouter,
2929
invitation: invitationRouter,
3030
project: projectRouter,
31+
branch: branchRouter,
3132
settings: settingsRouter,
3233
chat: chatRouter,
3334
frame: frameRouter,
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { branches, branchInsertSchema, branchUpdateSchema, frames, fromDbBranch } from '@onlook/db';
2+
import { eq } from 'drizzle-orm';
3+
import { z } from 'zod';
4+
import { createTRPCRouter, protectedProcedure } from '../../trpc';
5+
6+
export const branchRouter = createTRPCRouter({
7+
8+
getByProjectId: protectedProcedure
9+
.input(
10+
z.object({
11+
projectId: z.string(),
12+
}),
13+
)
14+
.query(async ({ ctx, input }) => {
15+
const dbBranch = await ctx.db.query.branches.findFirst({
16+
where: eq(branches.projectId, input.projectId),
17+
});
18+
return fromDbBranch(dbBranch);
19+
}),
20+
create: protectedProcedure
21+
.input(branchInsertSchema)
22+
.mutation(async ({ ctx, input }) => {
23+
try {
24+
await ctx.db.insert(branches).values(input);
25+
return true;
26+
} catch (error) {
27+
console.error('Error creating frame', error);
28+
return false;
29+
}
30+
}),
31+
update: protectedProcedure.input(branchUpdateSchema).mutation(async ({ ctx, input }) => {
32+
try {
33+
await ctx.db
34+
.update(branches)
35+
.set(input)
36+
.where(
37+
eq(branches.id, input.id)
38+
);
39+
return true;
40+
} catch (error) {
41+
console.error('Error updating frame', error);
42+
return false;
43+
}
44+
}),
45+
delete: protectedProcedure
46+
.input(
47+
z.object({
48+
frameId: z.string(),
49+
}),
50+
)
51+
.mutation(async ({ ctx, input }) => {
52+
try {
53+
await ctx.db.delete(frames).where(eq(frames.id, input.frameId));
54+
return true;
55+
} catch (error) {
56+
console.error('Error deleting frame', error);
57+
return false;
58+
}
59+
}),
60+
});

packages/db/src/schema/project/branch.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { eq, relations } from 'drizzle-orm';
22
import { boolean, pgTable, text, timestamp, uniqueIndex, uuid, varchar } from 'drizzle-orm/pg-core';
33
import { createInsertSchema, createUpdateSchema } from 'drizzle-zod';
4+
import { z } from 'zod';
45
import { frames } from '../canvas/frame';
56
import { projects } from './project';
67

@@ -31,7 +32,9 @@ export const branches = pgTable('branches', {
3132
).enableRLS();
3233

3334
export const branchInsertSchema = createInsertSchema(branches);
34-
export const branchUpdateSchema = createUpdateSchema(branches);
35+
export const branchUpdateSchema = createUpdateSchema(branches, {
36+
id: z.string().uuid(),
37+
});
3538

3639
export const branchRelations = relations(branches, ({ one, many }) => ({
3740
project: one(projects, {

0 commit comments

Comments
 (0)