Skip to content

Commit

Permalink
Implemented Workspace Visibility Update & Added Fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
R1c4rdCo5t4 committed Jun 27, 2024
1 parent ab10f70 commit 3d79172
Show file tree
Hide file tree
Showing 19 changed files with 106 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
.manage-members-dialog {
.manage-workspace-dialog {
ul {
padding: 0;
margin: 0;

button {
background-color: transparent;
border: none;
cursor: pointer;
color: black;
font-size: larger;
}
}

li {
Expand All @@ -11,4 +19,8 @@
align-items: center;
list-style: none;
}

> button {
margin: auto;
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
import Dialog from '@ui/components/dialog/Dialog';
import { FaUsers } from 'react-icons/fa6';
import { RxCross1 } from 'react-icons/rx';
import { MdManageAccounts } from 'react-icons/md';
import './ManageMembersDialog.scss';

type ManageMembersDialog = {
type ManageWorkspaceDialogProps = {
members: string[];
onAddMember: (email: string) => void;
onRemoveMember: (email: string) => void;
isPrivate: boolean;
toggleVisibility: () => void;
};

function ManageMembersDialog({ members, onAddMember, onRemoveMember }: ManageMembersDialog) {
function ManageWorkspaceDialog({
members,
onAddMember,
onRemoveMember,
isPrivate,
toggleVisibility,
}: ManageWorkspaceDialogProps) {
return (
<Dialog
title="Manage Members"
title="Manage Workspace"
fields={[{ name: 'Add new member', label: 'User email' }]}
onSubmit={values => {
onAddMember(values['Add new member']);
}}
submitText="Add Member"
extraContent={
<div className="manage-members-dialog">
<div className="manage-workspace-dialog">
<button onClick={toggleVisibility}>Make {isPrivate ? 'Public' : 'Private'}</button>
<h4>Current Members</h4>
<ul>
{members?.map(member => (
Expand All @@ -38,11 +47,11 @@ function ManageMembersDialog({ members, onAddMember, onRemoveMember }: ManageMem
}
>
<>
<FaUsers />
Members
<MdManageAccounts />
Manage
</>
</Dialog>
);
}

export default ManageMembersDialog;
export default ManageWorkspaceDialog;
1 change: 1 addition & 0 deletions code/client/src/ui/pages/workspaces/Workspaces.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ function Workspaces() {
onGetMembers={() => operations.getWorkspaceMembers(workspace.id)}
onAddMember={email => operations.addWorkspaceMember(workspace.id, email)}
onRemoveMember={email => operations.removeWorkspaceMember(workspace.id, email)}
toggleVisibility={() => operations.updateWorkspace(workspace.id, { isPrivate: !workspace.isPrivate })}
/>
))}
</DataTable>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import PopupMenu from '@ui/components/popup-menu/PopupMenu';
import { ReactNode, useEffect, useState } from 'react';
import { MdDelete, MdEdit } from 'react-icons/md';
import ManageMembersDialog from '@ui/pages/workspace/components/ManageMembersDialog';
import { useAuth } from '@/contexts/auth/useAuth';
import ManageWorkspaceDialog from '@ui/pages/workspace/components/ManageWorkspaceDialog';

type WorkspaceContextMenuProps = {
children: ReactNode;
Expand All @@ -11,6 +11,8 @@ type WorkspaceContextMenuProps = {
onGetMembers: () => Promise<string[]>;
onAddMember: (email: string) => Promise<void>;
onRemoveMember: (email: string) => Promise<void>;
isPrivate: boolean;
toggleVisibility: () => Promise<void>;
};

function WorkspaceContextMenu({
Expand All @@ -20,6 +22,8 @@ function WorkspaceContextMenu({
onGetMembers,
onAddMember,
onRemoveMember,
isPrivate,
toggleVisibility,
}: WorkspaceContextMenuProps) {
const [members, setMembers] = useState<string[]>([]);
const [isMember, setIsMember] = useState(false);
Expand All @@ -41,7 +45,13 @@ function WorkspaceContextMenu({
<MdEdit />
Rename
</button>
<ManageMembersDialog members={members} onAddMember={onAddMember} onRemoveMember={onRemoveMember} />
<ManageWorkspaceDialog
members={members}
onAddMember={onAddMember}
onRemoveMember={onRemoveMember}
isPrivate={isPrivate}
toggleVisibility={toggleVisibility}
/>
<button onClick={onDelete}>
<MdDelete />
Delete
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type WorkspacePreviewProps = {
onGetMembers: () => Promise<string[]>;
onAddMember: (email: string) => Promise<void>;
onRemoveMember: (email: string) => Promise<void>;
toggleVisibility: () => Promise<void>;
};

function WorkspaceView({
Expand All @@ -26,6 +27,7 @@ function WorkspaceView({
onGetMembers,
onAddMember,
onRemoveMember,
toggleVisibility,
}: WorkspacePreviewProps) {
const { component, isEditing, setIsEditing } = useEditing(workspace.name, onRename);
const [isSelected, setSelected] = useState(selected);
Expand Down Expand Up @@ -57,6 +59,8 @@ function WorkspaceView({
onGetMembers={onGetMembers}
onAddMember={onAddMember}
onRemoveMember={onRemoveMember}
isPrivate={workspace.isPrivate}
toggleVisibility={toggleVisibility}
>
<Link to={`/workspaces/${workspace.id}`}>{WorkspaceComponent}</Link>
</WorkspaceContextMenu>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,9 @@ function workspacesHandlers(services: Services, io: Server) {
const updateWorkspace = async (req: Request, res: Response) => {
const { wid } = req.params;
if (!wid) throw new InvalidParameterError('Workspace id is required');
const { name } = req.body as WorkspaceMeta;
if (!name) throw new InvalidParameterError('Workspace name is required');
await services.workspaces.updateWorkspace(wid, name);
io.emit('updatedWorkspace', { id: wid, name } as WorkspaceMeta);
const newProps = req.body as Partial<WorkspaceMeta>;
await services.workspaces.updateWorkspace(wid, newProps);
io.emit('updatedWorkspace', { id: wid, ...newProps } as WorkspaceMeta);
httpResponse.noContent(res).send();
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const cursorColorsMap = new Map<string, string>();

function onCursorChange() {
return (socket: Socket, range: any) => {
const documentId = rooms.document.get(socket.id)?.id;
const documentId = rooms.documents.get(socket.id)?.id;
if (!documentId) return;

if (!range) deleteCursor(socket, documentId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ function onJoinDocument() {
if (!user) return;

// join the document room
rooms.document.join(socket, documentId, user);
rooms.documents.join(socket, documentId, user);

// broadcast to all clients in the document
socket.in(documentId).emit('joinedDocument', [{ ...user, color: getCursorColor(socket.id) }]);

// send the clients that are already in the document to the new client
const room = rooms.document.getRoom(documentId)!;
const room = rooms.documents.getRoom(documentId)!;
const users = room
.getClients()
.map(client => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { getUserFromSocket } from '@controllers/ws/utils';

function onLeaveDocument() {
return function (socket: Socket) {
const documentId = rooms.document.get(socket.id)?.id;
const documentId = rooms.documents.get(socket.id)?.id;
if (!documentId) return;

// leave the document room
rooms.document.leave(socket);
rooms.documents.leave(socket);

// delete cursor
deleteCursor(socket, documentId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function onOperation(service: DocumentsService) {
return async (socket: Socket, operations: Operation[]) => {
if (!operations) throw new InvalidParameterError('Operations are required');

const { id, wid } = rooms.document.get(socket.id);
const { id, wid } = rooms.documents.get(socket.id);
if (!id) throw new ForbiddenError('Not in a room');

socket.broadcast.to(id).emit('operations', operations);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function onJoinWorkspace(service: WorkspacesService) {
if (!user || !members.includes(user.email)) return;

// join the workspace room
rooms.workspace.join(socket, id, user);
rooms.workspaces.join(socket, id, user);
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Socket } from 'socket.io';
import rooms from '@controllers/ws/rooms/rooms';
import onLeaveDocument from '@controllers/ws/events/document/onLeaveDocument';

function onLeaveWorkspace() {
return function (socket: Socket) {
rooms.workspace.leave(socket);
// check if the user is in a document room
const isInDocument = rooms.documents.isInRoom(socket.id);
if (isInDocument) onLeaveDocument()(socket);

// leave the workspace room
rooms.workspaces.leave(socket);
};
}

Expand Down
24 changes: 17 additions & 7 deletions code/server/src/controllers/ws/initSocketEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,23 @@ export default function initSocketEvents(events: Record<string, SocketHandler>)
});

socket.on('disconnect', reason => {
// check if the user is in a document room
const isInDocument = rooms.document.isInRoom(socket.id);
if (isInDocument) onLeaveDocument()(socket);
// check if the user is in a workspace room
const isInWorkspace = rooms.workspace.isInRoom(socket.id);
if (isInWorkspace) onLeaveWorkspace()(socket);
logger.logInfo('Client disconnected: ' + reason);
try {
// check if the user is in a workspace room
const isInWorkspace = rooms.workspaces.isInRoom(socket.id);
if (isInWorkspace) {
// check if the user is in a document room
const isInDocument = rooms.documents.isInRoom(socket.id);
if (isInDocument) {
onLeaveDocument()(socket);
}
// leave the workspace room
onLeaveWorkspace()(socket);
}
} catch (e: any) {
logger.logError(e);
} finally {
logger.logInfo('Client disconnected: ' + reason);
}
});
};
}
4 changes: 2 additions & 2 deletions code/server/src/controllers/ws/rooms/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,14 @@ function isInWorkspaceRoom(socketId: string) {
}

export default {
document: {
documents: {
join: joinDocument,
leave: leaveDocument,
get: getDocument,
getRoom: getDocumentRoom,
isInRoom: isInDocumentRoom,
},
workspace: {
workspaces: {
join: joinWorkspace,
leave: leaveWorkspace,
get: getWorkspace,
Expand Down
6 changes: 2 additions & 4 deletions code/server/src/databases/memory/MemoryWorkspacesDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,15 @@ export class MemoryWorkspacesDB implements WorkspacesRepository {
return workspace.resources;
}

async updateWorkspace(id: string, name: string): Promise<void> {
async updateWorkspace(id: string, newProps: Partial<WorkspaceMeta>): Promise<void> {
const workspace = Memory.workspaces[id];
if (!workspace) throw new NotFoundError(`Workspace not found`);

Object.assign(workspace, { name });
Object.assign(workspace, newProps);
}

async deleteWorkspace(id: string): Promise<void> {
const workspace = Memory.workspaces[id];
if (!workspace) throw new NotFoundError(`Workspace not found`);

delete Memory.workspaces[id];
}

Expand Down
11 changes: 9 additions & 2 deletions code/server/src/databases/postgres/PostgresUsersDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ export class PostgresUsersDB implements UsersRepository {
}

async getUser(id: string): Promise<User> {
const results: User[] = await sql`select * from "user" where id = ${id}`;
const results: User[] = await sql`
select id, name, email, created_at as "createdAt"
from "user"
where id = ${id}
`;
if (isEmpty(results)) throw new NotFoundError('User not found');
return results[0];
}
Expand All @@ -25,6 +29,9 @@ export class PostgresUsersDB implements UsersRepository {
}

async getUsers(): Promise<User[]> {
return await sql`select * from "user"`;
return await sql`
select id, name, email, created_at as "createdAt"
from "user"
`;
}
}
6 changes: 4 additions & 2 deletions code/server/src/databases/postgres/PostgresWorkspacesDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,12 @@ export class PostgresWorkspacesDB implements WorkspacesRepository {
).map(r => r.resources);
}

async updateWorkspace(id: string, name: string): Promise<void> {
async updateWorkspace(id: string, newProps: Partial<WorkspaceMeta>): Promise<void> {
const { isPrivate, ...rest } = newProps;
const compatible = isPrivate ? { private: isPrivate, ...rest } : rest;
const results = await sql`
update workspace
set name = ${name}
set ${sql(compatible)}
where id = ${id}
returning id
`;
Expand Down
4 changes: 2 additions & 2 deletions code/server/src/databases/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ export interface WorkspacesRepository {
/**
* Update a workspace in the database
* @param id
* @param name
* @param newProps
*/
updateWorkspace: (id: string, name: string) => Promise<void>;
updateWorkspace: (id: string, newProps: Partial<WorkspaceMeta>) => Promise<void>;
/**
* Delete a workspace from the database
* @param id
Expand Down
9 changes: 6 additions & 3 deletions code/server/src/services/WorkspacesService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ export class WorkspacesService {
return id;
}

async updateWorkspace(id: string, name: string) {
async updateWorkspace(id: string, newProps: Partial<WorkspaceMeta>) {
validateId(id);
validateName(name);
await this.databases.workspaces.updateWorkspace(id, name);
if (newProps.name) validateName(newProps.name);
if (newProps.id) throw new Error('Cannot update workspace id');
if (newProps.createdAt) throw new Error('Cannot update workspace createdAt');
if (newProps.members) throw new Error('Cannot update workspace members');
await this.databases.workspaces.updateWorkspace(id, newProps);
}

async deleteWorkspace(id: string) {
Expand Down

0 comments on commit 3d79172

Please sign in to comment.