Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit 3e3867e

Browse files
committed
feat(Cards): add cards layout
1 parent e567d0e commit 3e3867e

File tree

8 files changed

+236
-23
lines changed

8 files changed

+236
-23
lines changed

ui/public/postgres.png

116 KB
Loading
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Add } from "@mui/icons-material";
2+
import { Card, Typography } from "@mui/material";
3+
import * as React from "react";
4+
import { NewDatabaseDialog } from "./NewDatabaseDialog";
5+
6+
export const AddNewDatabaseCard = () => {
7+
const [open, setOpen] = React.useState(false);
8+
9+
const handleClickOpen = () => {
10+
setOpen(true);
11+
};
12+
13+
return (
14+
<>
15+
<Card
16+
variant="outlined"
17+
sx={{
18+
cursor: "default",
19+
width: "265px",
20+
height: "205px",
21+
display: "flex",
22+
flexDirection: "column",
23+
alignItems: "center",
24+
justifyContent: "center",
25+
}}
26+
onClick={handleClickOpen}
27+
>
28+
<Add fontSize="large" />
29+
<Typography variant="body1">Add New Database</Typography>
30+
</Card>
31+
<NewDatabaseDialog open={open} onClose={() => setOpen(false)} />
32+
</>
33+
);
34+
};

ui/src/components/CardMenu.tsx

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import * as React from 'react';
2+
import IconButton from '@mui/material/IconButton';
3+
import Menu from '@mui/material/Menu';
4+
import MenuItem from '@mui/material/MenuItem';
5+
import MoreVertIcon from '@mui/icons-material/MoreVert';
6+
import { Divider } from '@mui/material';
7+
import { Delete, Edit, Stop } from '@mui/icons-material';
8+
9+
const options = [
10+
'None',
11+
'Atria',
12+
'Callisto',
13+
'Dione',
14+
'Ganymede',
15+
'Hangouts Call',
16+
'Luna',
17+
'Oberon',
18+
'Phobos',
19+
'Pyxis',
20+
'Sedna',
21+
'Titania',
22+
'Triton',
23+
'Umbriel',
24+
];
25+
26+
const ITEM_HEIGHT = 48;
27+
28+
export function CardMenu() {
29+
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
30+
const open = Boolean(anchorEl);
31+
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
32+
setAnchorEl(event.currentTarget);
33+
event.stopPropagation();
34+
};
35+
const handleClose = () => {
36+
setAnchorEl(null);
37+
};
38+
39+
return (
40+
<div>
41+
<IconButton
42+
aria-label="more"
43+
id="long-button"
44+
aria-controls={open ? 'long-menu' : undefined}
45+
aria-expanded={open ? 'true' : undefined}
46+
aria-haspopup="true"
47+
onClick={handleClick}
48+
>
49+
<MoreVertIcon />
50+
</IconButton>
51+
<Menu
52+
id="long-menu"
53+
MenuListProps={{
54+
'aria-labelledby': 'long-button',
55+
}}
56+
anchorEl={anchorEl}
57+
open={open}
58+
onClose={handleClose}
59+
PaperProps={{
60+
style: {
61+
maxHeight: ITEM_HEIGHT * 4.5,
62+
width: '20ch',
63+
},
64+
}}
65+
>
66+
<MenuItem key={'Start Container'} onClick={handleClose}>
67+
<Edit fontSize="small" sx={{marginRight: 1}}/>{'Edit Database'}
68+
</MenuItem>
69+
<MenuItem key={'Stop Container'} onClick={handleClose}>
70+
<Delete fontSize="small" sx={{marginRight: 1}}/>{'Delete Database'}
71+
</MenuItem>
72+
<Divider />
73+
<MenuItem key={'Change Connection'} onClick={handleClose}>
74+
<Stop fontSize="small" sx={{marginRight: 1}}/>{'Stop Container'}
75+
</MenuItem>
76+
</Menu>
77+
</div>
78+
);
79+
}

ui/src/components/DatabaseCard.tsx

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
1-
import { Box, Card, Typography } from "@mui/material";
1+
import { Circle } from "@mui/icons-material";
2+
import { Box, Card, CardContent, Slide, Typography } from "@mui/material";
3+
import { TransitionProps } from "@mui/material/transitions";
24
import * as React from "react";
35
import { useCurrentDatabaseContext } from "../CurrentDatabaseContext";
6+
import { useTestConnection } from "../hooks/useTestConnection";
47
import { IDBConnection } from "../utils/types";
8+
import { CardMenu } from "./CardMenu";
9+
import DatabaseViewerDialog from "./DatabaseViewerDialog";
10+
11+
const Transition = React.forwardRef(function Transition(
12+
props: TransitionProps & {
13+
children: React.ReactElement;
14+
},
15+
ref: React.Ref<unknown>
16+
) {
17+
return <Slide direction="left" ref={ref} {...props} />;
18+
});
519

620
export const DatabaseCard = ({ database }: { database: IDBConnection }) => {
21+
const [open, setOpen] = React.useState(false);
22+
const { isConnected } = useTestConnection(database);
723
const { setDatabase } = useCurrentDatabaseContext();
824

925
const handleClickOpen = () => {
@@ -14,15 +30,69 @@ export const DatabaseCard = ({ database }: { database: IDBConnection }) => {
1430
<>
1531
<Card
1632
variant="outlined"
17-
onClick={handleClickOpen}
1833
sx={{
19-
padding: 2,
34+
cursor: "default",
35+
width: "265px",
36+
height: "205px",
2037
}}
2138
>
22-
<Typography>Name: {database.name}</Typography>
23-
<Typography>Image: {database.image}</Typography>
39+
<CardContent
40+
sx={{
41+
display: "flex",
42+
flexDirection: "column",
43+
position: "relative",
44+
}}
45+
>
46+
<Box sx={{ textAlign: "center", paddingY: 2 }}>
47+
<img
48+
src={`/${database.image}.png`}
49+
width="40px"
50+
style={{
51+
filter: isConnected ? "none" : "grayscale(100%)",
52+
}}
53+
/>
54+
</Box>
55+
<Box
56+
sx={{
57+
position: "absolute",
58+
top: 12,
59+
right: 12,
60+
}}
61+
>
62+
<CardMenu />
63+
</Box>
64+
<Box display="inline-flex" gap={0.5} alignItems="center">
65+
<Circle
66+
sx={{
67+
fontSize: "8px",
68+
color: isConnected ? "green" : "red",
69+
}}
70+
/>
71+
<Typography variant="body2" sx={{ fontSize: "10px" }}>
72+
{isConnected ? "CONNECTED" : "UNABLE TO CONNECT"}
73+
</Typography>
74+
</Box>
75+
<Typography
76+
variant="h3"
77+
onClick={handleClickOpen}
78+
sx={{
79+
cursor: "pointer",
80+
}}
81+
>
82+
{database.name}
83+
</Typography>
84+
</CardContent>
2485
</Card>
2586

87+
{open && (
88+
<DatabaseViewerDialog
89+
database={database}
90+
open={open}
91+
onClose={() => setOpen(false)}
92+
fullScreen
93+
TransitionComponent={Transition}
94+
/>
95+
)}
2696
</>
2797
);
2898
};

ui/src/components/DatabasesCards.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { TransitionProps } from "@mui/material/transitions";
33
import * as React from "react";
44
import { useCurrentDatabaseContext } from "../CurrentDatabaseContext";
55
import { useGetDatabaseConnections } from "../hooks/useGetDatabaseConnections";
6+
import { AddNewDatabaseCard } from "./AddNewDatabaseCard";
67
import { DatabaseCard } from "./DatabaseCard";
78
import DatabaseViewerDialog from "./DatabaseViewerDialog";
8-
import { NewDatabaseButton } from "./NewDatabaseButton";
99

1010
const Transition = React.forwardRef(function Transition(
1111
props: TransitionProps & {
@@ -31,7 +31,7 @@ export const DatabasesCards = () => {
3131
{databases.map((database) => (
3232
<DatabaseCard key={database.id} database={database} />
3333
))}
34-
<NewDatabaseButton />
34+
<AddNewDatabaseCard />
3535
{ currentDatabase && (
3636
<DatabaseViewerDialog
3737
database={currentDatabase}

ui/src/components/NewDatabaseButton.tsx

Lines changed: 0 additions & 16 deletions
This file was deleted.

ui/src/hooks/useTestConnection.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { createDockerDesktopClient } from "@docker/extension-api-client";
2+
import { useEffect, useState } from "react";
3+
import { IDBConnection } from "../utils/types";
4+
5+
const ddClient = createDockerDesktopClient();
6+
export const useTestConnection = (database: IDBConnection) => {
7+
const [isConnected, setIsConnected] = useState<boolean>(false);
8+
const [loading, setLoading] = useState<boolean>(false);
9+
const [error, setError] = useState<string | null>(null);
10+
11+
const testConnection = async () => {
12+
setLoading(true);
13+
try {
14+
const result = await ddClient.extension.host!.cli.exec("usql", [
15+
database.connectionString,
16+
"-J", // json output
17+
"-q", // quiet, do not print connection string
18+
"-c", // execute the command
19+
"'select 1'",
20+
]);
21+
console.log(result);
22+
setLoading(false);
23+
24+
if (result.stderr) {
25+
setIsConnected(false);
26+
return false;
27+
}
28+
setIsConnected(true);
29+
return true;
30+
} catch (err: any) {
31+
setIsConnected(false);
32+
setLoading(false);
33+
console.log(err);
34+
ddClient.desktopUI.toast.error(err.stderr);
35+
return false;
36+
//setError(err?.message);
37+
}
38+
};
39+
40+
useEffect(() => {
41+
testConnection();
42+
}, []);
43+
44+
return { testConnection, isConnected, loading, error };
45+
};

ui/src/utils/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export interface IDBConnection {
33
name: string;
44
connectionString: string;
55
image: string;
6+
connected?: boolean;
67
}
78

89
export interface IDatabaseProvider {

0 commit comments

Comments
 (0)