Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions apps/web/client/src/components/store/editor/canvas/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { api } from '@/trpc/client';
import { DefaultSettings } from '@onlook/constants';
import { fromCanvas } from '@onlook/db';
import type { Canvas, Frame, RectPosition } from '@onlook/models';
import { RealtimeEventType, type Canvas, type Frame, type RectPosition } from '@onlook/models';
import { debounce } from 'lodash';
import { makeAutoObservable } from 'mobx';
import type { ProjectManager } from '../../project/manager';
import type { EditorEngine } from '../engine';
import type { UserManager } from '../../user/manager';

type SettingsObserver = (settings: Frame) => void;

Expand All @@ -14,7 +16,11 @@ export class CanvasManager {
private _position: RectPosition = DefaultSettings.PAN_POSITION;
private settingsObservers: Map<string, Set<SettingsObserver>> = new Map();

constructor(private projects: ProjectManager) {
constructor(
private editorEngine: EditorEngine,
private projects: ProjectManager,
private userManager: UserManager,
) {
this._position = this.getDefaultPanPosition();
makeAutoObservable(this);
}
Expand All @@ -23,6 +29,19 @@ export class CanvasManager {
this.id = canvas.id;
this.scale = canvas.scale ?? DefaultSettings.SCALE;
this.position = canvas.position ?? this.getDefaultPanPosition();

if (this.userManager.user) {
this.editorEngine.realtime.send({
event: RealtimeEventType.USER_UPDATED,
payload: {
...this.userManager.user,
position: {
x: this.position.x,
y: this.position.y,
},
},
});
}
}

getDefaultPanPosition(): RectPosition {
Expand Down Expand Up @@ -68,6 +87,19 @@ export class CanvasManager {
if (!success) {
console.error('Failed to update canvas');
}

if (this.userManager.user) {
this.editorEngine.realtime.send({
event: RealtimeEventType.USER_UPDATED,
payload: {
...this.userManager.user,
position: {
x: this.position.x,
y: this.position.y,
},
},
});
}
}

clear() {
Expand Down
4 changes: 3 additions & 1 deletion apps/web/client/src/components/store/editor/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { StateManager } from './state';
import { StyleManager } from './style';
import { TextEditingManager } from './text';
import { ThemeManager } from './theme';
import { RealtimeManager } from './realtime/manager';

export class EditorEngine {
readonly chat: ChatManager;
Expand Down Expand Up @@ -52,13 +53,14 @@ export class EditorEngine {
constructor(
private projectManager: ProjectManager,
private userManager: UserManager,
readonly realtime: RealtimeManager,
) {
this.chat = new ChatManager(this, this.projectManager, this.userManager);
this.pages = new PagesManager(this, this.projectManager);
this.image = new ImageManager(this, this.projectManager);
this.theme = new ThemeManager(this, this.projectManager);
this.font = new FontManager(this, this.projectManager);
this.canvas = new CanvasManager(this.projectManager)
this.canvas = new CanvasManager(this, this.projectManager, this.userManager);
this.frames = new FramesManager(this, this.projectManager);
makeAutoObservable(this);
}
Expand Down
19 changes: 18 additions & 1 deletion apps/web/client/src/components/store/editor/frames/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { WebFrameView } from '@/app/project/[id]/_components/canvas/frame/w
import { api } from '@/trpc/client';
import { sendAnalytics } from '@/utils/analytics';
import { fromFrame } from '@onlook/db';
import { FrameType, type Frame, type WebFrame } from '@onlook/models';
import { FrameType, RealtimeEventType, type Frame, type WebFrame } from '@onlook/models';
import { makeAutoObservable } from 'mobx';
import { v4 as uuid } from 'uuid';
import type { ProjectManager } from '../../project/manager';
Expand Down Expand Up @@ -185,6 +185,10 @@ export class FramesManager {
this.disposeFrame(data.frame.id);
this.frames = this.frames.filter((f) => f.id !== id);
this.trackFrameAction('deleted');
this.editorEngine.realtime.send({
event: RealtimeEventType.FRAME_DELETED,
payload: data.frame,
});
} else {
console.error('Failed to delete frame');
}
Expand All @@ -200,6 +204,10 @@ export class FramesManager {

if (success) {
this.frames.push(FrameImpl.fromJSON(frame));
this.editorEngine.realtime.send({
event: RealtimeEventType.FRAME_CREATED,
payload: frame,
});
this.trackFrameAction('created');
} else {
console.error('Failed to create frame');
Expand Down Expand Up @@ -242,6 +250,10 @@ export class FramesManager {
const data = this.frameIdToData.get(id);
if (data) {
this.frameIdToData.set(id, { ...data, frame: updatedFrame });
this.editorEngine.realtime.send({
event: RealtimeEventType.FRAME_UPDATED,
payload: updatedFrame,
});
}
}

Expand All @@ -267,6 +279,11 @@ export class FramesManager {
if (!success) {
console.error('Failed to update frame');
}

this.editorEngine.realtime.send({
event: RealtimeEventType.FRAME_UPDATED,
payload: frame,
});
} catch (error) {
console.error('Failed to update frame', error);
}
Expand Down
3 changes: 2 additions & 1 deletion apps/web/client/src/components/store/editor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { createContext, useContext } from 'react';
import { projectManager } from '../project';
import { userManager } from '../user';
import { EditorEngine } from './engine';
import { realtimeManager } from './realtime';

const editorEngine = new EditorEngine(projectManager, userManager);
const editorEngine = new EditorEngine(projectManager, userManager, realtimeManager);
const EditorEngineContext = createContext(editorEngine);
export const useEditorEngine = () => useContext(EditorEngineContext);
8 changes: 8 additions & 0 deletions apps/web/client/src/components/store/editor/realtime/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createContext, useContext } from 'react';
import { RealtimeManager } from './manager';
import { projectManager } from '../../project';
import { userManager } from '../../user';

export const realtimeManager = new RealtimeManager(projectManager, userManager);
const RealtimeContext = createContext(realtimeManager);
export const useRealtimeManager = () => useContext(RealtimeContext);
79 changes: 79 additions & 0 deletions apps/web/client/src/components/store/editor/realtime/manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { makeAutoObservable } from 'mobx';
import { RealtimeChannel } from '@supabase/supabase-js';
import { ProjectManager } from '../../project/manager';
import { createClient } from '@/utils/supabase/client';
import type { RealtimeEvent, RealtimeUser } from '@onlook/models';
import type { UserManager } from '../../user/manager';

export class RealtimeManager {
readonly supabase = createClient();
private _channel: RealtimeChannel | null = null;
private _users: Record<string, RealtimeUser> = {};
private _isSubscribed = false;

constructor(
private projectManager: ProjectManager,
private userManager: UserManager,
) {
makeAutoObservable(this);

this.init();
}

private init() {
if (!this.projectManager.project?.id) {
console.error('Project ID is not set');
return;
}

// Clean up any existing subscriptions
this.clean();

this._channel = this.supabase.channel(this.projectManager.project.id);
this._channel
.on('broadcast', { event: '*' }, (payload) => {
//console.log('payload', payload);
})
Comment on lines +34 to +36
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The broadcast event handler currently doesn't process incoming events - it only logs them to the console (which is commented out). For the realtime functionality to work properly, this handler should parse the payload and update the internal state by calling the appropriate methods (add, update, remove) based on the event type. Without this implementation, the manager will receive events but won't update its state, making the realtime features ineffective.

Suggested change
.on('broadcast', { event: '*' }, (payload) => {
//console.log('payload', payload);
})
.on('broadcast', { event: '*' }, (payload) => {
const { event, data } = payload;
if (event.startsWith('add:')) {
this.add(data);
} else if (event.startsWith('update:')) {
this.update(data);
} else if (event.startsWith('remove:')) {
this.remove(data);
}
})

Spotted by Diamond

Is this helpful? React 👍 or 👎 to let us know.

.subscribe((status) => {
if (status === 'SUBSCRIBED') {
this._isSubscribed = true;
}
});
}

private add(user: RealtimeUser) {
this._users[user.id] = user;
}

private remove(id: string) {
delete this._users[id];
}

private update(user: RealtimeUser) {
this._users[user.id] = user;
}

private getUsers() {
return this._users;
}

clean() {
this._channel?.unsubscribe();
this._channel = null;
this._users = {};
this._isSubscribed = false;
}

send(event: RealtimeEvent) {
if (!this._isSubscribed || !this._channel) {
console.error('Not subscribed to realtime channel');
return;
}

this._channel.send({
type: 'broadcast',
event: event.event,
payload: event,
});
}
}
2 changes: 1 addition & 1 deletion apps/web/preload/dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17202,5 +17202,5 @@ export {
penpalParent
};

//# debugId=4CD4957A43BF04C164756E2164756E21
//# debugId=E88E6828F5B0F8C064756E2164756E21
//# sourceMappingURL=index.js.map
1 change: 1 addition & 0 deletions packages/models/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './editor/';
export * from './element/';
export * from './ide/';
export * from './llm/';
export * from './realtime/';
export * from './pages/';
export * from './project/';
export * from './run/';
Expand Down
50 changes: 50 additions & 0 deletions packages/models/src/realtime/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { UserMetadata } from '../user';
import type { Frame } from '../project';

export interface RealtimeUser extends UserMetadata {
position: {
x: number;
y: number;
};
}

export enum RealtimeEventType {
USER_ADDED = 'user_added',
USER_REMOVED = 'user_removed',
USER_UPDATED = 'user_updated',
FRAME_UPDATED = 'frame_updated',
FRAME_DELETED = 'frame_deleted',
FRAME_CREATED = 'frame_created',
}

export interface FrameCreatedEvent {
event: RealtimeEventType.FRAME_CREATED;
payload: Frame;
}

export interface FrameUpdatedEvent {
event: RealtimeEventType.FRAME_UPDATED;
payload: Frame;
}

export interface FrameDeletedEvent {
event: RealtimeEventType.FRAME_DELETED;
payload: Frame;
}

export interface UserAddedEvent {
event: RealtimeEventType.USER_ADDED;
payload: RealtimeUser;
}

export interface UserUpdatedEvent {
event: RealtimeEventType.USER_UPDATED;
payload: RealtimeUser;
}

export type RealtimeEvent =
| FrameCreatedEvent
| FrameUpdatedEvent
| FrameDeletedEvent
| UserAddedEvent
| UserUpdatedEvent;