Skip to content

Commit a1f433f

Browse files
committed
feat: add server creation
1 parent 2e7b823 commit a1f433f

12 files changed

+262
-41
lines changed

src/app/core/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { getRequirements } from './requirements';
2+
export { getServers } from './servers';

src/app/core/requirements.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,5 @@ export const getRequirements = (
5050
cache[windowId] = instance;
5151
}
5252

53-
return new RequirementsFacade();
53+
return instance;
5454
};

src/app/core/servers.ts

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { BrowserWindow, IpcMainEvent, IpcMainInvokeEvent } from 'electron';
2+
import { Server, ServersResource } from '../../shared/types/servers';
3+
import { createFileIfNotExisting, loadFromFile, saveFile } from './storage';
4+
5+
export default class ServersFacade implements ServersResource {
6+
static SERVERS_FILE = 'servers.json';
7+
8+
constructor() {
9+
createFileIfNotExisting(ServersFacade.SERVERS_FILE, []);
10+
}
11+
12+
async create(payload: Server): Promise<Server> {
13+
return loadFromFile<Server[]>(ServersFacade.SERVERS_FILE)
14+
.then(servers => servers.concat(payload))
15+
.then(servers => saveFile(ServersFacade.SERVERS_FILE, servers))
16+
.then(() => payload);
17+
}
18+
19+
read(businessKey: string): Promise<Server>;
20+
read(): Promise<Server[]>;
21+
read(businessKey?: unknown): Promise<Server | Server[]> {
22+
return loadFromFile<Server[]>(ServersFacade.SERVERS_FILE).then(servers =>
23+
businessKey
24+
? servers.find(server => server.businessKey === businessKey)
25+
: servers
26+
);
27+
}
28+
29+
update(businessKey: string, payload: Server): Promise<Server> {
30+
return loadFromFile<Server[]>(ServersFacade.SERVERS_FILE)
31+
.then(servers =>
32+
servers.map(server =>
33+
server.businessKey === businessKey ? payload : server
34+
)
35+
)
36+
.then(servers => saveFile(ServersFacade.SERVERS_FILE, servers))
37+
.then(() => payload);
38+
}
39+
40+
delete(businessKey: string): Promise<void> {
41+
return loadFromFile<Server[]>(ServersFacade.SERVERS_FILE)
42+
.then(servers =>
43+
servers.filter(server => server.businessKey !== businessKey)
44+
)
45+
.then(servers => saveFile(ServersFacade.SERVERS_FILE, servers));
46+
}
47+
}
48+
49+
const cache: {
50+
[windowId: string]: ServersFacade;
51+
} = {};
52+
53+
export const getServers = (
54+
e: IpcMainEvent | IpcMainInvokeEvent
55+
): ServersFacade => {
56+
const sourceWindow = BrowserWindow.fromWebContents(e.sender);
57+
if (!sourceWindow) {
58+
throw new Error('Could not resolve current window');
59+
}
60+
61+
const windowId = sourceWindow.id;
62+
63+
let instance = cache[windowId];
64+
if (!instance) {
65+
instance = new ServersFacade();
66+
cache[windowId] = instance;
67+
}
68+
69+
return instance;
70+
};

src/app/core/storage.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,30 @@
1-
import { app } from "electron";
1+
import { app } from 'electron';
2+
import { existsSync, promises as fs } from 'fs';
3+
import path from 'path';
24

35
export function getUserDir() {
46
return app.getPath('appData');
57
}
8+
9+
export function fromJson<T>(content: string): T {
10+
return JSON.parse(content);
11+
}
12+
13+
export async function loadFromFile<T>(filename: string): Promise<T> {
14+
const filepath = path.join(getUserDir(), filename);
15+
const content = await fs.readFile(filepath, 'utf8');
16+
return fromJson<T>(content);
17+
}
18+
19+
export function saveFile<T>(filename: string, data: T) {
20+
const filepath = path.join(getUserDir(), filename);
21+
const content = JSON.stringify(data, null, 2);
22+
return fs.writeFile(filepath, content);
23+
}
24+
25+
export function createFileIfNotExisting<T>(filename: string, data: T) {
26+
const exists = existsSync(path.join(getUserDir(), filename));
27+
if (!exists) {
28+
saveFile(filename, data);
29+
}
30+
}

src/app/ipc-main.ts

+26-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,37 @@
11
import { IpcMainInvokeEvent, ipcMain } from 'electron';
22
import * as events from '../shared/events';
3-
import { getRequirements } from './core';
3+
import { Server } from '../shared/types/servers';
4+
import { getRequirements, getServers } from './core';
45

56
function registerRequirementsIPCMainHandlers() {
6-
ipcMain.handle(events.TIP_GET_REQUIREMENTS, (e: IpcMainInvokeEvent) =>
7+
ipcMain.handle(events.TIP_REQUIREMENTS_GET, (e: IpcMainInvokeEvent) =>
78
getRequirements(e).getRequirements()
89
);
910
}
1011

12+
function registerServersIPCMainHandlers() {
13+
ipcMain.handle(
14+
events.TIP_SERVERS_CREATE,
15+
(e: IpcMainInvokeEvent, payload: Server) => getServers(e).create(payload)
16+
);
17+
ipcMain.handle(
18+
events.TIP_SERVERS_READ,
19+
(e: IpcMainInvokeEvent, businessKey?: string) =>
20+
getServers(e).read(businessKey)
21+
);
22+
ipcMain.handle(
23+
events.TIP_SERVERS_UPDATE,
24+
(e: IpcMainInvokeEvent, businessKey: string, payload: Server) =>
25+
getServers(e).update(businessKey, payload)
26+
);
27+
ipcMain.handle(
28+
events.TIP_SERVERS_DELETE,
29+
(e: IpcMainInvokeEvent, businessKey: string) =>
30+
getServers(e).delete(businessKey)
31+
);
32+
}
33+
1134
export const registerIPCMainHandlers = (): void => {
1235
registerRequirementsIPCMainHandlers();
36+
registerServersIPCMainHandlers();
1337
};

src/app/preload.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
import { contextBridge, ipcRenderer } from 'electron';
22
import { TipManagerResource } from '../shared/api';
33
import * as events from '../shared/events';
4+
import { Server } from '../shared/types/servers';
45

56
const tipManagerResource: TipManagerResource = {
67
requirements: {
7-
getRequirements: () => ipcRenderer.invoke(events.TIP_GET_REQUIREMENTS),
8+
getRequirements: () => ipcRenderer.invoke(events.TIP_REQUIREMENTS_GET),
9+
},
10+
servers: {
11+
create: (payload: Server) =>
12+
ipcRenderer.invoke(events.TIP_SERVERS_CREATE, payload),
13+
read: (businessKey?: string) =>
14+
ipcRenderer.invoke(events.TIP_SERVERS_READ, businessKey),
15+
update: (businessKey: string, payload: Server) =>
16+
ipcRenderer.invoke(events.TIP_SERVERS_UPDATE, businessKey, payload),
17+
delete: (businessKey: string) =>
18+
ipcRenderer.invoke(events.TIP_SERVERS_DELETE, businessKey),
819
},
920
};
1021

src/shared/api.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { RequirementsResource } from './types/requirements';
2+
import { ServersResource } from './types/servers';
23

34
export interface TipManagerResource {
45
requirements: RequirementsResource;
6+
servers: ServersResource;
57
}

src/shared/events.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1-
export const TIP_GET_REQUIREMENTS = 'TIP_GET_REQUIREMENTS';
1+
export const TIP_REQUIREMENTS_GET = 'TIP_REQUIREMENTS_GET';
2+
3+
export const TIP_SERVERS_CREATE = 'TIP_SERVERS_CREATE';
4+
export const TIP_SERVERS_READ = 'TIP_SERVERS_READ';
5+
export const TIP_SERVERS_UPDATE = 'TIP_SERVERS_UPDATE';
6+
export const TIP_SERVERS_DELETE = 'TIP_SERVERS_DELETE';

src/shared/types/servers.ts

+13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,17 @@
11
export interface Server {
2+
businessKey?: string;
23
name: string;
34
url: string;
45
}
6+
7+
export interface ServersResource {
8+
create(payload: Server): Promise<Server>;
9+
10+
read(businessKey: string): Promise<Server>;
11+
12+
read(): Promise<Server[]>;
13+
14+
update(businessKey: string, payload: Server): Promise<Server>;
15+
16+
delete(businessKey: string): Promise<void>;
17+
}
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { Server, ServersResource } from '../../../shared/types/servers';
2+
import { BaseResource } from '../../base-resource';
3+
4+
export class ServersResourceImpl
5+
extends BaseResource
6+
implements ServersResource
7+
{
8+
async create(payload: Server): Promise<Server> {
9+
return this.base.servers.create(payload);
10+
}
11+
12+
async read(businessKey: string): Promise<Server>;
13+
async read(): Promise<Server[]>;
14+
async read(businessKey?: string): Promise<Server | Server[]> {
15+
return businessKey
16+
? this.base.servers.read(businessKey)
17+
: this.base.servers.read();
18+
}
19+
20+
async update(businessKey: string, payload: Server): Promise<Server> {
21+
return this.base.servers.update(businessKey, payload);
22+
}
23+
24+
async delete(businessKey: string): Promise<void> {
25+
return this.base.servers.delete(businessKey);
26+
}
27+
}

src/ui/modules/servers/servers.tsx

+77-34
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,36 @@
11
import AddIcon from '@mui/icons-material/Add';
2+
import CheckIcon from '@mui/icons-material/Check';
3+
import CloseIcon from '@mui/icons-material/Close';
24
import Box from '@mui/material/Box';
35
import Button from '@mui/material/Button';
6+
import IconButton from '@mui/material/IconButton';
47
import Paper from '@mui/material/Paper';
58
import Table from '@mui/material/Table';
69
import TableBody from '@mui/material/TableBody';
710
import TableCell from '@mui/material/TableCell';
811
import TableContainer from '@mui/material/TableContainer';
912
import TableHead from '@mui/material/TableHead';
1013
import TableRow from '@mui/material/TableRow';
14+
import TextField from '@mui/material/TextField';
1115
import Typography from '@mui/material/Typography';
12-
import React, { useState } from 'react';
16+
import React, { useEffect, useMemo, useRef, useState } from 'react';
1317
import { Server } from '../../../shared/types/servers';
18+
import { ServersResourceImpl } from './servers-service';
19+
1420
export default function Servers() {
15-
const [servers] = useState<Server[]>([]);
21+
const service = useMemo(() => new ServersResourceImpl(), []);
22+
const [add, setAdd] = useState<boolean>(false);
23+
const [servers, setServers] = useState<Server[]>([]);
24+
useEffect(() => {
25+
console.log('load servers');
26+
service.read().then(servers => setServers(servers));
27+
}, []);
28+
const createServer = (server: Server) =>
29+
service
30+
.create(server)
31+
.then(() => service.read())
32+
.then(servers => setServers(servers))
33+
.then(() => setAdd(false));
1634

1735
return (
1836
<React.Fragment>
@@ -21,64 +39,89 @@ export default function Servers() {
2139
display: 'flex',
2240
alignItems: 'center',
2341
justifyContent: 'space-between',
24-
p: 3,
42+
mb: 2,
2543
}}
2644
>
2745
<Typography variant="h5">Servers</Typography>
2846

29-
<Button variant="outlined" startIcon={<AddIcon />}>
30-
Add
47+
<Button
48+
variant="outlined"
49+
startIcon={<AddIcon />}
50+
onClick={() => setAdd(!add)}
51+
>
52+
Create
3153
</Button>
3254
</Box>
33-
<BasicTable />
55+
56+
{add && (
57+
<CreateServer onSubmit={createServer} onCancel={() => setAdd(false)} />
58+
)}
59+
60+
<ServersTable servers={servers} />
3461
</React.Fragment>
3562
);
3663
}
3764

38-
function createData(
39-
name: string,
40-
calories: number,
41-
fat: number,
42-
carbs: number,
43-
protein: number
44-
) {
45-
return { name, calories, fat, carbs, protein };
46-
}
65+
function CreateServer(props: {
66+
onSubmit: (server: Server) => void;
67+
onCancel: () => void;
68+
}) {
69+
const nameRef = useRef<HTMLInputElement>();
70+
const urlRef = useRef<HTMLInputElement>();
4771

48-
const rows = [
49-
createData('Frozen yoghurt', 159, 6.0, 24, 4.0),
50-
createData('Ice cream sandwich', 237, 9.0, 37, 4.3),
51-
createData('Eclair', 262, 16.0, 24, 6.0),
52-
createData('Cupcake', 305, 3.7, 67, 4.3),
53-
createData('Gingerbread', 356, 16.0, 49, 3.9),
54-
];
72+
return (
73+
<Box
74+
component="form"
75+
sx={{
76+
'& > :not(style)': { m: 1 },
77+
mb: 2,
78+
display: 'flex',
79+
alignItems: 'center',
80+
}}
81+
noValidate
82+
autoComplete="off"
83+
>
84+
<TextField id="name" label="Name" variant="standard" inputRef={nameRef} />
85+
<TextField id="url" label="URL" variant="standard" inputRef={urlRef} />
86+
87+
<IconButton
88+
aria-label="submit"
89+
onClick={() =>
90+
props.onSubmit({
91+
name: nameRef.current.value,
92+
url: urlRef.current.value,
93+
})
94+
}
95+
>
96+
<CheckIcon />
97+
</IconButton>
98+
<IconButton aria-label="cancel" onClick={() => props.onCancel()}>
99+
<CloseIcon />
100+
</IconButton>
101+
</Box>
102+
);
103+
}
55104

56-
function BasicTable() {
105+
function ServersTable({ servers }: { servers: Server[] }) {
57106
return (
58107
<TableContainer component={Paper}>
59108
<Table sx={{ minWidth: 650 }} aria-label="simple table">
60109
<TableHead>
61110
<TableRow>
62-
<TableCell>Dessert (100g serving)</TableCell>
63-
<TableCell align="right">Calories</TableCell>
64-
<TableCell align="right">Fat&nbsp;(g)</TableCell>
65-
<TableCell align="right">Carbs&nbsp;(g)</TableCell>
66-
<TableCell align="right">Protein&nbsp;(g)</TableCell>
111+
<TableCell>Name</TableCell>
112+
<TableCell>URL</TableCell>
67113
</TableRow>
68114
</TableHead>
69115
<TableBody>
70-
{rows.map(row => (
116+
{servers.map((row, index) => (
71117
<TableRow
72-
key={row.name}
118+
key={index}
73119
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
74120
>
75121
<TableCell component="th" scope="row">
76122
{row.name}
77123
</TableCell>
78-
<TableCell align="right">{row.calories}</TableCell>
79-
<TableCell align="right">{row.fat}</TableCell>
80-
<TableCell align="right">{row.carbs}</TableCell>
81-
<TableCell align="right">{row.protein}</TableCell>
124+
<TableCell>{row.url}</TableCell>
82125
</TableRow>
83126
))}
84127
</TableBody>

0 commit comments

Comments
 (0)