Skip to content
This repository was archived by the owner on Jun 11, 2024. It is now read-only.

Commit 73459d6

Browse files
committed
전체적으로 변경 및 추가
1 parent f9103fe commit 73459d6

36 files changed

+1135
-213
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ index.node
33
**/node_modules
44
**/.DS_Store
55
npm-debug.log*
6-
dist
6+
dist
7+
release

.vscode/typescript.code-snippets

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"Styled Components Props": {
3+
"prefix": [
4+
"pro"
5+
],
6+
"body": [
7+
"${props => props${0}}"
8+
],
9+
"description": "Styled Components Props"
10+
}
11+
}

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ A template for using electron quickly.
77
- App framework: [`electron`](https://www.electronjs.org/)
88
- App build tool: [`electron-builder`](https://www.electron.build/)
99
- App storage: [`electron-store`](https://github.com/sindresorhus/electron-store)
10+
- App auto updater: [`electron-updater`](https://www.electron.build/auto-update)
1011
- Bundle tool: [`vite`](https://vitejs.dev/)
1112
- Frontend framework: `react` + `typescript`
1213
- Code style: `eslint` + `prettier` + [`@trivago/prettier-plugin-sort-imports`](https://github.com/trivago/prettier-plugin-sort-imports)
13-
- File-system based router: [`generouted`](https://github.com/oedotme/generouted) + [`@tanstack/react-location`](https://react-location.tanstack.com/overview)
14+
- File system based router: [`react-router-dom v6`](https://reactrouter.com/docs/en/v6) + custom (src/components/FileSystemRoutes)
1415
- CSS: [`styled-components`](https://styled-components.com/)
1516
- State management library: [`recoil`](https://hookstate.js.org/)
1617
- Date: [`dayjs`](https://day.js.org/)

app/index.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { protocols } from '../electron-builder.json';
2+
import { app, BrowserWindow, Menu, nativeImage, Tray } from 'electron';
3+
4+
import { join, resolve } from 'path';
5+
6+
import './ipcs/general';
7+
import './ipcs/store';
8+
import './ipcs/update';
9+
import { runDeepLinkResolver } from './utils/deepLink';
10+
11+
global.win = null;
12+
13+
const PROTOCOL = protocols.name;
14+
const IS_MAC = process.platform === 'darwin';
15+
const DEV_URL = `http://localhost:3000`;
16+
const PROD_FILE_PATH = join(__dirname, '../index.html');
17+
18+
const RESOURCES_PATH = app.isPackaged
19+
? join(process.resourcesPath, 'resources')
20+
: join(app.getAppPath(), 'resources');
21+
22+
const icon = nativeImage.createFromPath(
23+
`${RESOURCES_PATH}/icons/${IS_MAC ? '[email protected]' : '[email protected]'}`,
24+
);
25+
26+
const trayIcon = icon.resize({ width: 20, height: 20 });
27+
28+
app.setAsDefaultProtocolClient(PROTOCOL);
29+
30+
const gotTheLock = app.requestSingleInstanceLock();
31+
32+
if (!gotTheLock) {
33+
app.quit();
34+
process.exit(0);
35+
}
36+
37+
const createWindow = () => {
38+
if (global.win) {
39+
if (global.win.isMinimized()) global.win.restore();
40+
global.win.focus();
41+
return;
42+
}
43+
44+
global.win = new BrowserWindow({
45+
icon,
46+
width: 800,
47+
height: 600,
48+
show: false,
49+
webPreferences: {
50+
preload: join(__dirname, 'preload/index.js'),
51+
},
52+
});
53+
54+
if (app.isPackaged) {
55+
global.win.loadFile(PROD_FILE_PATH);
56+
} else {
57+
global.win.loadURL(DEV_URL);
58+
global.win?.webContents.toggleDevTools();
59+
}
60+
61+
global.win.on('ready-to-show', () => {
62+
global.win?.show();
63+
});
64+
};
65+
66+
app.on('activate', () => {
67+
createWindow();
68+
});
69+
70+
app.on('second-instance', (_, argv) => {
71+
console.log('second-instance');
72+
if (!IS_MAC) {
73+
const url = argv.find(arg => arg.startsWith(`${PROTOCOL}://`));
74+
75+
if (url) {
76+
runDeepLinkResolver(url);
77+
}
78+
}
79+
80+
createWindow();
81+
});
82+
83+
app.on('window-all-closed', () => {
84+
global.win = null;
85+
});
86+
87+
app.on('open-url', (_, url) => {
88+
runDeepLinkResolver(url);
89+
});
90+
91+
app.whenReady().then(() => {
92+
createWindow();
93+
94+
let tray = new Tray(trayIcon);
95+
96+
const contextMenu = Menu.buildFromTemplate([
97+
{ label: 'view app screen', type: 'normal', click: () => createWindow() },
98+
{ type: 'separator' },
99+
{ label: 'quit', role: 'quit', type: 'normal' },
100+
]);
101+
102+
tray.on('double-click', () => createWindow());
103+
tray.setToolTip('TemplateApp');
104+
tray.setContextMenu(contextMenu);
105+
});

app/ipcs/general.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { BrowserWindow, ipcMain, shell } from 'electron';
2+
3+
declare global {
4+
var win: BrowserWindow | null;
5+
}
6+
7+
export type AppControlAction = 'devtools' | 'minimize' | 'maximize' | 'close';
8+
9+
// 창 닫기, 최대화, 최소화 같은 컨트롤 기능
10+
ipcMain.on('appControl', async (_, action: AppControlAction) => {
11+
switch (action) {
12+
case 'devtools': {
13+
global.win?.webContents.toggleDevTools();
14+
break;
15+
}
16+
17+
case 'minimize': {
18+
global.win?.minimize();
19+
break;
20+
}
21+
22+
case 'maximize': {
23+
global.win?.isMaximized() ? global.win?.unmaximize() : global.win?.maximize();
24+
break;
25+
}
26+
27+
case 'close': {
28+
global.win?.close();
29+
break;
30+
}
31+
}
32+
});
33+
34+
// 기본 브라우저로 링크 열기
35+
ipcMain.on('openExternal', async (_, link) => {
36+
return shell.openExternal(link);
37+
});

app/ipcs/store.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { ipcMain } from 'electron';
2+
3+
import { configStore } from '../stores/config';
4+
5+
ipcMain.handle('getConfig', async () => {
6+
return configStore.store;
7+
});
8+
9+
ipcMain.handle('setConfig', async (e, config) => {
10+
return (configStore.store = config);
11+
});

app/ipcs/update.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { app, ipcMain } from 'electron';
2+
import { autoUpdater } from 'electron-updater';
3+
4+
import { updateStore } from '../stores/update';
5+
import { initlizeUpdater } from '../utils/update';
6+
7+
ipcMain.handle('getVersion', async () => {
8+
return app.getVersion();
9+
});
10+
11+
ipcMain.handle('getUpdateStatus', async () => {
12+
return updateStore.get('status');
13+
});
14+
15+
ipcMain.on('checkForUpdate', async () => {
16+
autoUpdater.checkForUpdates();
17+
});
18+
19+
ipcMain.on('quitAndInstall', async () => {
20+
autoUpdater.quitAndInstall();
21+
});
22+
23+
ipcMain.once('initlizeUpdater', async () => {
24+
initlizeUpdater();
25+
});

app/preload/index.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { contextBridge, ipcRenderer } from 'electron';
2+
3+
import { AppControlAction } from '../ipcs/general';
4+
import { ConfigStoreValues } from '../stores/config';
5+
import { UpdateEvent, UpdateStatus } from '../utils/update';
6+
7+
export interface ElectronRendererContext {
8+
// general
9+
openExternal: (link: string) => void;
10+
appControl: (action: AppControlAction) => void;
11+
12+
// update
13+
initlizeUpdater: () => void;
14+
checkForUpdate: () => void;
15+
quitAndInstall: () => void;
16+
getVersion: () => Promise<string>;
17+
getUpdateStatus: () => Promise<UpdateStatus>;
18+
onUpdate: (callback: (event: UpdateEvent, data: any) => void) => void;
19+
20+
// config store
21+
getConfig: () => Promise<ConfigStoreValues>;
22+
setConfig: (config: ConfigStoreValues) => Promise<ConfigStoreValues>;
23+
}
24+
25+
const electronContext: ElectronRendererContext = {
26+
appControl: action => ipcRenderer.send('appControl', action),
27+
openExternal: link => ipcRenderer.send('openExternal', link),
28+
29+
initlizeUpdater: () => ipcRenderer.send('initlizeUpdater'),
30+
checkForUpdate: () => ipcRenderer.send('checkForUpdate'),
31+
quitAndInstall: () => ipcRenderer.send('quitAndInstall'),
32+
getVersion: () => ipcRenderer.invoke('getVersion'),
33+
getUpdateStatus: () => ipcRenderer.invoke('getUpdateStatus'),
34+
onUpdate: callback => ipcRenderer.on('update', (_, event, data) => callback(event, data)),
35+
36+
getConfig: () => ipcRenderer.invoke('getConfig'),
37+
setConfig: config => ipcRenderer.invoke('setConfig', config),
38+
};
39+
40+
contextBridge.exposeInMainWorld('electron', electronContext);

app/stores/config.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import Store from 'electron-store';
2+
3+
export interface ConfigStoreValues {
4+
general: {
5+
theme: 'light' | 'dark';
6+
};
7+
}
8+
9+
export const configStore = new Store<ConfigStoreValues>({
10+
name: 'config',
11+
accessPropertiesByDotNotation: false,
12+
defaults: {
13+
general: {
14+
theme: 'dark',
15+
},
16+
},
17+
});

app/stores/update.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import Store from 'electron-store';
2+
3+
import { UpdateStatus } from '../utils/update';
4+
5+
export interface UpdateStoreValues {
6+
status: UpdateStatus;
7+
}
8+
9+
export const updateStore = new Store<UpdateStoreValues>({
10+
name: 'update',
11+
accessPropertiesByDotNotation: false,
12+
defaults: {
13+
status: {
14+
event: 'checking-for-update',
15+
data: null,
16+
time: new Date().getTime(),
17+
},
18+
},
19+
});

0 commit comments

Comments
 (0)