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

Commit

Permalink
전체적으로 변경 및 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
2skydev committed Aug 22, 2022
1 parent f9103fe commit 73459d6
Show file tree
Hide file tree
Showing 36 changed files with 1,135 additions and 213 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ index.node
**/node_modules
**/.DS_Store
npm-debug.log*
dist
dist
release
11 changes: 11 additions & 0 deletions .vscode/typescript.code-snippets
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"Styled Components Props": {
"prefix": [
"pro"
],
"body": [
"${props => props${0}}"
],
"description": "Styled Components Props"
}
}
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ A template for using electron quickly.
- App framework: [`electron`](https://www.electronjs.org/)
- App build tool: [`electron-builder`](https://www.electron.build/)
- App storage: [`electron-store`](https://github.com/sindresorhus/electron-store)
- App auto updater: [`electron-updater`](https://www.electron.build/auto-update)
- Bundle tool: [`vite`](https://vitejs.dev/)
- Frontend framework: `react` + `typescript`
- Code style: `eslint` + `prettier` + [`@trivago/prettier-plugin-sort-imports`](https://github.com/trivago/prettier-plugin-sort-imports)
- File-system based router: [`generouted`](https://github.com/oedotme/generouted) + [`@tanstack/react-location`](https://react-location.tanstack.com/overview)
- File system based router: [`react-router-dom v6`](https://reactrouter.com/docs/en/v6) + custom (src/components/FileSystemRoutes)
- CSS: [`styled-components`](https://styled-components.com/)
- State management library: [`recoil`](https://hookstate.js.org/)
- Date: [`dayjs`](https://day.js.org/)
Expand Down
105 changes: 105 additions & 0 deletions app/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { protocols } from '../electron-builder.json';
import { app, BrowserWindow, Menu, nativeImage, Tray } from 'electron';

import { join, resolve } from 'path';

import './ipcs/general';
import './ipcs/store';
import './ipcs/update';
import { runDeepLinkResolver } from './utils/deepLink';

global.win = null;

const PROTOCOL = protocols.name;
const IS_MAC = process.platform === 'darwin';
const DEV_URL = `http://localhost:3000`;
const PROD_FILE_PATH = join(__dirname, '../index.html');

const RESOURCES_PATH = app.isPackaged
? join(process.resourcesPath, 'resources')
: join(app.getAppPath(), 'resources');

const icon = nativeImage.createFromPath(
`${RESOURCES_PATH}/icons/${IS_MAC ? 'logo@512.png' : 'logo@256.ico'}`,
);

const trayIcon = icon.resize({ width: 20, height: 20 });

app.setAsDefaultProtocolClient(PROTOCOL);

const gotTheLock = app.requestSingleInstanceLock();

if (!gotTheLock) {
app.quit();
process.exit(0);
}

const createWindow = () => {
if (global.win) {
if (global.win.isMinimized()) global.win.restore();
global.win.focus();
return;
}

global.win = new BrowserWindow({
icon,
width: 800,
height: 600,
show: false,
webPreferences: {
preload: join(__dirname, 'preload/index.js'),
},
});

if (app.isPackaged) {
global.win.loadFile(PROD_FILE_PATH);
} else {
global.win.loadURL(DEV_URL);
global.win?.webContents.toggleDevTools();
}

global.win.on('ready-to-show', () => {
global.win?.show();
});
};

app.on('activate', () => {
createWindow();
});

app.on('second-instance', (_, argv) => {
console.log('second-instance');
if (!IS_MAC) {
const url = argv.find(arg => arg.startsWith(`${PROTOCOL}://`));

if (url) {
runDeepLinkResolver(url);
}
}

createWindow();
});

app.on('window-all-closed', () => {
global.win = null;
});

app.on('open-url', (_, url) => {
runDeepLinkResolver(url);
});

app.whenReady().then(() => {
createWindow();

let tray = new Tray(trayIcon);

const contextMenu = Menu.buildFromTemplate([
{ label: 'view app screen', type: 'normal', click: () => createWindow() },
{ type: 'separator' },
{ label: 'quit', role: 'quit', type: 'normal' },
]);

tray.on('double-click', () => createWindow());
tray.setToolTip('TemplateApp');
tray.setContextMenu(contextMenu);
});
37 changes: 37 additions & 0 deletions app/ipcs/general.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { BrowserWindow, ipcMain, shell } from 'electron';

declare global {
var win: BrowserWindow | null;
}

export type AppControlAction = 'devtools' | 'minimize' | 'maximize' | 'close';

// 창 닫기, 최대화, 최소화 같은 컨트롤 기능
ipcMain.on('appControl', async (_, action: AppControlAction) => {
switch (action) {
case 'devtools': {
global.win?.webContents.toggleDevTools();
break;
}

case 'minimize': {
global.win?.minimize();
break;
}

case 'maximize': {
global.win?.isMaximized() ? global.win?.unmaximize() : global.win?.maximize();
break;
}

case 'close': {
global.win?.close();
break;
}
}
});

// 기본 브라우저로 링크 열기
ipcMain.on('openExternal', async (_, link) => {
return shell.openExternal(link);
});
11 changes: 11 additions & 0 deletions app/ipcs/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ipcMain } from 'electron';

import { configStore } from '../stores/config';

ipcMain.handle('getConfig', async () => {
return configStore.store;
});

ipcMain.handle('setConfig', async (e, config) => {
return (configStore.store = config);
});
25 changes: 25 additions & 0 deletions app/ipcs/update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { app, ipcMain } from 'electron';
import { autoUpdater } from 'electron-updater';

import { updateStore } from '../stores/update';
import { initlizeUpdater } from '../utils/update';

ipcMain.handle('getVersion', async () => {
return app.getVersion();
});

ipcMain.handle('getUpdateStatus', async () => {
return updateStore.get('status');
});

ipcMain.on('checkForUpdate', async () => {
autoUpdater.checkForUpdates();
});

ipcMain.on('quitAndInstall', async () => {
autoUpdater.quitAndInstall();
});

ipcMain.once('initlizeUpdater', async () => {
initlizeUpdater();
});
40 changes: 40 additions & 0 deletions app/preload/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { contextBridge, ipcRenderer } from 'electron';

import { AppControlAction } from '../ipcs/general';
import { ConfigStoreValues } from '../stores/config';
import { UpdateEvent, UpdateStatus } from '../utils/update';

export interface ElectronRendererContext {
// general
openExternal: (link: string) => void;
appControl: (action: AppControlAction) => void;

// update
initlizeUpdater: () => void;
checkForUpdate: () => void;
quitAndInstall: () => void;
getVersion: () => Promise<string>;
getUpdateStatus: () => Promise<UpdateStatus>;
onUpdate: (callback: (event: UpdateEvent, data: any) => void) => void;

// config store
getConfig: () => Promise<ConfigStoreValues>;
setConfig: (config: ConfigStoreValues) => Promise<ConfigStoreValues>;
}

const electronContext: ElectronRendererContext = {
appControl: action => ipcRenderer.send('appControl', action),
openExternal: link => ipcRenderer.send('openExternal', link),

initlizeUpdater: () => ipcRenderer.send('initlizeUpdater'),
checkForUpdate: () => ipcRenderer.send('checkForUpdate'),
quitAndInstall: () => ipcRenderer.send('quitAndInstall'),
getVersion: () => ipcRenderer.invoke('getVersion'),
getUpdateStatus: () => ipcRenderer.invoke('getUpdateStatus'),
onUpdate: callback => ipcRenderer.on('update', (_, event, data) => callback(event, data)),

getConfig: () => ipcRenderer.invoke('getConfig'),
setConfig: config => ipcRenderer.invoke('setConfig', config),
};

contextBridge.exposeInMainWorld('electron', electronContext);
17 changes: 17 additions & 0 deletions app/stores/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Store from 'electron-store';

export interface ConfigStoreValues {
general: {
theme: 'light' | 'dark';
};
}

export const configStore = new Store<ConfigStoreValues>({
name: 'config',
accessPropertiesByDotNotation: false,
defaults: {
general: {
theme: 'dark',
},
},
});
19 changes: 19 additions & 0 deletions app/stores/update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Store from 'electron-store';

import { UpdateStatus } from '../utils/update';

export interface UpdateStoreValues {
status: UpdateStatus;
}

export const updateStore = new Store<UpdateStoreValues>({
name: 'update',
accessPropertiesByDotNotation: false,
defaults: {
status: {
event: 'checking-for-update',
data: null,
time: new Date().getTime(),
},
},
});
27 changes: 27 additions & 0 deletions app/utils/deepLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { dialog } from 'electron';

import { MatchResult, match } from 'path-to-regexp';

export type DeepLinkResolvers = Record<string, (data: MatchResult<any>) => void>;

export const runDeepLinkResolver = (url: string) => {
const pathname = url.replace('templateapp://', '/');

for (const path in resolvers) {
const data = match(path)(pathname);

if (data) {
resolvers[path](data);
break;
}
}
};

export const resolvers: DeepLinkResolvers = {
'/test/:id': async ({ params }) => {
dialog.showMessageBox({
title: 'Deep Link',
message: `${params.id}`,
});
},
};
47 changes: 47 additions & 0 deletions app/utils/update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import log from 'electron-log';
import { autoUpdater } from 'electron-updater';

import { updateStore } from '../stores/update';

export interface UpdateStatus {
event: UpdateEvent;
data: any;
time: number;
}

export type UpdateEvent =
| 'checking-for-update'
| 'update-available'
| 'update-not-available'
| 'error'
| 'download-progress'
| 'update-downloaded';

export const handleUpdateEvent = (event: UpdateEvent) => {
return (data?: any) => {
if (event !== 'download-progress') {
updateStore.set('status', {
event,
data,
time: new Date().getTime(),
});
}

global.win?.webContents.send('update', event, data);
};
};

export const initlizeUpdater = () => {
autoUpdater.logger = log;
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.fullChangelog = true;

autoUpdater.on('checking-for-update', handleUpdateEvent('checking-for-update'));
autoUpdater.on('update-available', handleUpdateEvent('update-available'));
autoUpdater.on('update-not-available', handleUpdateEvent('update-not-available'));
autoUpdater.on('download-progress', handleUpdateEvent('download-progress'));
autoUpdater.on('update-downloaded', handleUpdateEvent('update-downloaded'));
autoUpdater.on('error', handleUpdateEvent('error'));

autoUpdater.checkForUpdates();
};
Loading

0 comments on commit 73459d6

Please sign in to comment.