Skip to content

Commit

Permalink
Merge pull request #181 from boostcampwm-2024/feat/client/#175
Browse files Browse the repository at this point in the history
Feat/client/#175
  • Loading branch information
Gdm0714 authored Dec 3, 2024
2 parents f823e3e + 8c14bf4 commit edbf44d
Show file tree
Hide file tree
Showing 64 changed files with 1,733 additions and 602 deletions.
2 changes: 2 additions & 0 deletions apps/client/.env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
VITE_API_URL=http://localhost:3000
VITE_MODE=dev
2 changes: 2 additions & 0 deletions apps/client/.env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
VITE_API_URL=https://api.cloudcanvas.kro.kr
VITE_MODE=prod
15 changes: 10 additions & 5 deletions apps/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,27 @@
"keywords": [],
"type": "module",
"scripts": {
"start": "serve dist",
"dev": "vite",
"build": "tsc -b && vite build",
"clean": "rm -rf dist"
"dev": "vite --mode development",
"build:prod": "tsc -b && vite build --mode production",
"build:dev": "tsc -b && vite build --mode development",
"clean": "rm -rf dist",
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@mui/icons-material": "^6.1.5",
"@mui/material": "^6.1.5",
"@types/validator": "^13.12.2",
"nanoid": "^5.0.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^7.0.1",
"react-select": "^5.8.3",
"react-syntax-highlighter": "^15.6.1",
"react-type-animation": "^3.2.0",
"terraform": "file:../../packages/terraform"
"terraform": "file:../../packages/terraform",
"validator": "^13.12.0"
},
"devDependencies": {
"@types/react": "^18.3.11",
Expand Down
30 changes: 3 additions & 27 deletions apps/client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,13 @@
import CloudGraph from '@/src/CloudGraph';
import Header from '@components/Layout/Header';
import Sidebar from '@components/Layout/Sidebar';
import NetworksBar from '@components/NCloud/NetworksBar/index';
import PropertiesBar from '@components/NCloud/PropertiesBar';
import Box from '@mui/material/Box';

function App() {
export const App = () => {
return (
<>
<Box
sx={{
height: '100%',
display: 'flex',
}}
>
<Sidebar />
<Box
sx={{
width: '100%',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
}}
>
<Header />
<CloudGraph />
</Box>
</Box>

<CloudGraph />
<NetworksBar />
<PropertiesBar />
</>
);
}

export default App;
};
156 changes: 88 additions & 68 deletions apps/client/src/CloudGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Graph from '@components/Graph';
import GridBackground from '@components/GridBackground';
import Group from '@components/Group';
import Node from '@components/Node';
import NodeActions from '@components/NodeActions';
import { useEdgeContext } from '@contexts/EdgeContext';
import { useGraphContext } from '@contexts/GraphConetxt';
import { useGroupContext } from '@contexts/GroupContext';
Expand Down Expand Up @@ -70,18 +71,30 @@ export default () => {
useEffect(() => {
const handleContextMenu = (e: MouseEvent) => e.preventDefault();
const handleMouseDown = (e: MouseEvent) => {
if (!(e.target as HTMLElement).closest('.graph-ignore-select'))
if (
!(e.target as HTMLElement).closest('.graph-ignore-select') ||
isConnecting
)
clearSelection();
};
const handleClick = (e: MouseEvent) => {
if (isConnecting) {
closeConnection();
clearSelection();
document.body.style.cursor = 'default';
}
};

document.addEventListener('contextmenu', handleContextMenu);
document.addEventListener('mousedown', handleMouseDown);
document.addEventListener('click', handleClick);

return () => {
document.removeEventListener('contextmenu', handleContextMenu);
document.removeEventListener('mousedown', handleMouseDown);
document.removeEventListener('click', handleClick);
};
}, []);
}, [isConnecting, selectedNodeId]);

useEffect(() => {
prevDimensionRef.current = dimension;
Expand All @@ -98,76 +111,83 @@ export default () => {
}, [dimension]);

return (
<Graph>
<GridBackground />
{Object.values(groups).map((group) => (
<Group
key={group.id}
group={group}
bounds={getGroupBounds(group.id)}
onMove={moveGroup}
/>
))}
{Object.values(nodes).map((node) => (
<g key={node.id}>
<Node
node={node}
isSelected={selectedNodeId === node.id}
onMove={moveNode}
onSelect={selectNode}
onRemove={removeNode}
/>
<Connectors
node={node}
isSelected={selectedNodeId === node.id}
isConnecting={isConnecting}
onOpenConnection={openConnection}
onConnectConnection={connectConnection}
onCloseConnection={closeConnection}
<>
<Graph>
<GridBackground />
{Object.values(groups).map((group) => (
<Group
key={group.id}
group={group}
bounds={getGroupBounds(group.id)}
onMove={moveGroup}
/>
</g>
))}
{connection && (
<Connection
source={connection.source}
target={connection.target}
/>
)}

{edges &&
Object.values(edges).map((edge) => (
<g key={edge.id}>
<Edge
edge={edge}
selectedEdge={selectedEdge}
sourceConnector={
nodes[edge.source.id].connectors[
edge.source.connectorType
]
}
targetConnector={
nodes[edge.target.id].connectors[
edge.target.connectorType
]
}
onSelectEntire={selectEntireEdge}
onSelectSegment={selectSegEdge}
onSplit={splitEdge}
onRemove={removeEdge}
/>
{edge.bendingPoints.map((point, index) => (
<BendingPointer
key={`${edge.id}-${index}`}
edgeId={edge.id}
point={point}
index={index}
onMove={(newPoint) =>
moveBendingPointer(edge.id, index, newPoint)
))}
{edges &&
Object.values(edges).map((edge) => (
<g key={edge.id}>
<Edge
edge={edge}
selectedEdge={selectedEdge}
sourceConnector={
nodes[edge.source.id].connectors[
edge.source.connectorType
]
}
targetConnector={
nodes[edge.target.id].connectors[
edge.target.connectorType
]
}
onSelectEntire={selectEntireEdge}
onSelectSegment={selectSegEdge}
onSplit={splitEdge}
onRemove={removeEdge}
/>
))}
{edge.bendingPoints.map((point, index) => (
<BendingPointer
key={`${edge.id}-${index}`}
edgeId={edge.id}
point={point}
index={index}
onMove={(newPoint) =>
moveBendingPointer(
edge.id,
index,
newPoint,
)
}
/>
))}
</g>
))}
{connection && (
<Connection
source={connection.source}
target={connection.target}
/>
)}
{Object.values(nodes).map((node) => (
<g key={node.id}>
<Node
node={node}
isSelected={selectedNodeId === node.id}
onMove={moveNode}
onSelect={selectNode}
onRemove={removeNode}
/>
<Connectors node={node} />
</g>
))}
</Graph>
</Graph>
{selectedNodeId && (
<NodeActions
node={nodes[selectedNodeId]}
isConnecting={isConnecting}
onOpenConnection={openConnection}
onConnectConnection={connectConnection}
onRemoveNode={removeNode}
/>
)}
</>
);
};
15 changes: 15 additions & 0 deletions apps/client/src/Root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useLoaderData } from 'react-router-dom';
import CloudGraphProvider from '@components/CloudGraphProvider';
import Layout from '@components/Layout';

function Root() {
const loader = useLoaderData();

return (
<CloudGraphProvider initialData={loader?.data.architecture}>
<Layout />
</CloudGraphProvider>
);
}

export default Root;
15 changes: 15 additions & 0 deletions apps/client/src/apis/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const BASE_URL = import.meta.env.VITE_API_URL;
export const URLS = {
login: 'auth/login',
share: 'public-architectures', // POST
privateArchi: (id: string) => `private-architectures/${id}`,
};

export const urls = (path: keyof typeof URLS, slug?: any) => {
const urls = URLS[path];
if (typeof urls === 'function') {
return `${BASE_URL}/${urls(slug)}`;
}

return `${BASE_URL}/${urls}`;
};
43 changes: 43 additions & 0 deletions apps/client/src/apis/loaders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { LoaderFunctionArgs } from 'react-router-dom';
import { urls } from '.';
import { undefinedReviver } from '@utils';

export const rootLoader = async ({ params }: LoaderFunctionArgs) => {
const loginResponse = await fetch(urls('login'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
credentials: 'include',
});

if (!loginResponse.ok) {
throw new Response('Login failed', { status: loginResponse.status });
}

if (!params.id) return null;
const archiResponse = await fetch(urls('privateArchi', params.id), {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
});

if (!archiResponse.ok) {
throw new Response('Data fetch failed', {
status: archiResponse.status,
});
}

const text = await archiResponse.text();
let data;
try {
data = JSON.parse(text, undefinedReviver);
} catch (error) {
throw new Response('Invalid JSON response', { status: 500 });
}

return { data };
};
Loading

0 comments on commit edbf44d

Please sign in to comment.