Compare commits
4 Commits
1764f6fe96
...
87eca555a2
| Author | SHA1 | Date |
|---|---|---|
|
|
87eca555a2 | |
|
|
a64ae2aa7a | |
|
|
a9222c173f | |
|
|
d05827d341 |
|
|
@ -1,16 +1,18 @@
|
||||||
/* eslint-disable prettier/prettier */
|
/* eslint-disable prettier/prettier */
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { app, shell, BrowserWindow, ipcMain, screen, globalShortcut } from 'electron'
|
import { electronApp, is, optimizer } from '@electron-toolkit/utils'
|
||||||
import path, { join } from 'path'
|
import { app, BrowserWindow, globalShortcut, ipcMain, Menu, screen, shell, Tray } from 'electron'
|
||||||
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
|
||||||
import icon from '../../resources/icon.png?asset'
|
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
import path, { join } from 'path'
|
||||||
|
import icon from '../../resources/icon.png?asset'
|
||||||
|
|
||||||
|
let mainWindow: null | BrowserWindow = null
|
||||||
|
let isQuiting = false
|
||||||
function createWindow(): void {
|
function createWindow(): void {
|
||||||
// Get Screen width, height
|
// Get Screen width, height
|
||||||
const { width, height } = screen.getPrimaryDisplay().workAreaSize
|
const { width, height } = screen.getPrimaryDisplay().workAreaSize
|
||||||
|
|
||||||
const mainWindow = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
width: width / 2,
|
width: width / 2,
|
||||||
height: height / 2,
|
height: height / 2,
|
||||||
show: false,
|
show: false,
|
||||||
|
|
@ -26,7 +28,7 @@ function createWindow(): void {
|
||||||
mainWindow.setPosition(width / 2, height / 2)
|
mainWindow.setPosition(width / 2, height / 2)
|
||||||
|
|
||||||
mainWindow.on('ready-to-show', () => {
|
mainWindow.on('ready-to-show', () => {
|
||||||
mainWindow.show()
|
mainWindow?.show()
|
||||||
})
|
})
|
||||||
|
|
||||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||||
|
|
@ -46,19 +48,19 @@ function createWindow(): void {
|
||||||
mainWindow.webContents.once('did-finish-load', () => {
|
mainWindow.webContents.once('did-finish-load', () => {
|
||||||
const shortcut = 'Control+Shift+C'
|
const shortcut = 'Control+Shift+C'
|
||||||
|
|
||||||
mainWindow.on('focus', () => {
|
mainWindow?.on('focus', () => {
|
||||||
globalShortcut.register(shortcut, () => {
|
globalShortcut.register(shortcut, () => {
|
||||||
const pos = screen.getCursorScreenPoint()
|
const pos = screen.getCursorScreenPoint()
|
||||||
mainWindow.webContents.inspectElement(pos.x, pos.y)
|
mainWindow?.webContents.inspectElement(pos.x, pos.y)
|
||||||
mainWindow.webContents.focus() // optional: refocus back
|
mainWindow?.webContents.focus() // optional: refocus back
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
mainWindow.on('blur', () => {
|
mainWindow?.on('blur', () => {
|
||||||
globalShortcut.unregister(shortcut)
|
globalShortcut.unregister(shortcut)
|
||||||
})
|
})
|
||||||
|
|
||||||
mainWindow.on('closed', () => {
|
mainWindow?.on('closed', () => {
|
||||||
globalShortcut.unregister(shortcut)
|
globalShortcut.unregister(shortcut)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -70,6 +72,41 @@ function createWindow(): void {
|
||||||
} else {
|
} else {
|
||||||
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
|
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Khi bấm dấu X
|
||||||
|
mainWindow.on('close', (event) => {
|
||||||
|
if (!isQuiting) {
|
||||||
|
event.preventDefault()
|
||||||
|
mainWindow?.hide()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTray() {
|
||||||
|
const tray = new Tray(icon)
|
||||||
|
|
||||||
|
const contextMenu = Menu.buildFromTemplate([
|
||||||
|
{
|
||||||
|
label: 'Show',
|
||||||
|
click: () => {
|
||||||
|
mainWindow?.show()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Quit',
|
||||||
|
click: () => {
|
||||||
|
isQuiting = true
|
||||||
|
app.quit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
tray.setToolTip('Zulip notes')
|
||||||
|
tray.setContextMenu(contextMenu)
|
||||||
|
|
||||||
|
tray.on('double-click', () => {
|
||||||
|
mainWindow?.show()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method will be called when Electron has finished
|
// This method will be called when Electron has finished
|
||||||
|
|
@ -89,8 +126,12 @@ app.whenReady().then(() => {
|
||||||
// IPC test
|
// IPC test
|
||||||
ipcMain.on('ping', () => console.log('pong'))
|
ipcMain.on('ping', () => console.log('pong'))
|
||||||
|
|
||||||
|
// Create main window
|
||||||
createWindow()
|
createWindow()
|
||||||
|
|
||||||
|
// Create tray icon
|
||||||
|
createTray()
|
||||||
|
|
||||||
app.on('activate', function () {
|
app.on('activate', function () {
|
||||||
// On macOS it's common to re-create a window in the app when the
|
// On macOS it's common to re-create a window in the app when the
|
||||||
// dock icon is clicked and there are no other windows open.
|
// dock icon is clicked and there are no other windows open.
|
||||||
|
|
@ -162,3 +203,11 @@ ipcMain.handle('get-config-file', async () => {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('show-window', async () => {
|
||||||
|
mainWindow?.show()
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('hide-window', async () => {
|
||||||
|
mainWindow?.hide()
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import {
|
||||||
LoadingOverlay,
|
LoadingOverlay,
|
||||||
MantineProvider,
|
MantineProvider,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
Text,
|
|
||||||
Title,
|
Title,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from '@mantine/core'
|
} from '@mantine/core'
|
||||||
|
|
@ -18,18 +17,13 @@ import { Notifications } from '@mantine/notifications'
|
||||||
import '@mantine/notifications/styles.css'
|
import '@mantine/notifications/styles.css'
|
||||||
import { Product } from '@renderer/types/Product'
|
import { Product } from '@renderer/types/Product'
|
||||||
import { IconDotsVertical } from '@tabler/icons-react'
|
import { IconDotsVertical } from '@tabler/icons-react'
|
||||||
import moment from 'moment'
|
|
||||||
import { Suspense, useCallback, useEffect, useState } from 'react'
|
import { Suspense, useCallback, useEffect, useState } from 'react'
|
||||||
import { listHotItem } from './api/products'
|
import { listHotItem } from './api/products'
|
||||||
import CardItem from './components/CardItem'
|
import CardItem from './components/CardItem'
|
||||||
import ConfigModal from './components/ConfigModal'
|
import ConfigModal from './components/ConfigModal'
|
||||||
import { pusher } from './pusher'
|
import { pusher } from './pusher'
|
||||||
import { theme } from './theme'
|
import { theme } from './theme'
|
||||||
import {
|
import { myNotificationStore, playNotificationSound } from './utils/Notificaton'
|
||||||
myNotificationStore,
|
|
||||||
playNotificationSound,
|
|
||||||
prependNotification
|
|
||||||
} from './utils/Notificaton'
|
|
||||||
|
|
||||||
const PUSHER_CHANNEL = import.meta.env.VITE_PUSHER_CHANNEL
|
const PUSHER_CHANNEL = import.meta.env.VITE_PUSHER_CHANNEL
|
||||||
const PUSHER_EVENT = 'App\\Events\\MessagePushed'
|
const PUSHER_EVENT = 'App\\Events\\MessagePushed'
|
||||||
|
|
@ -38,7 +32,7 @@ function App(): React.JSX.Element {
|
||||||
const [newItems, setnewItems] = useState<Array<Product>>([])
|
const [newItems, setnewItems] = useState<Array<Product>>([])
|
||||||
const [hotItem, setHotItem] = useState<any>([])
|
const [hotItem, setHotItem] = useState<any>([])
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||||
const [timeout, setTimeout] = useState(2000)
|
const [timeoutNoti, setTimeoutNoti] = useState(2000)
|
||||||
|
|
||||||
const [opened, { open, close }] = useDisclosure(false)
|
const [opened, { open, close }] = useDisclosure(false)
|
||||||
|
|
||||||
|
|
@ -51,7 +45,7 @@ function App(): React.JSX.Element {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(data.noti_timeout * 1000)
|
setTimeoutNoti(data.noti_timeout * 1000)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('%csrc/renderer/src/App.tsx:48 error', 'color: #007acc;', error)
|
console.log('%csrc/renderer/src/App.tsx:48 error', 'color: #007acc;', error)
|
||||||
}
|
}
|
||||||
|
|
@ -66,35 +60,22 @@ function App(): React.JSX.Element {
|
||||||
if (data.data && data.data?.id) {
|
if (data.data && data.data?.id) {
|
||||||
setnewItems((prev: Array<Product>) => [data.data, ...prev])
|
setnewItems((prev: Array<Product>) => [data.data, ...prev])
|
||||||
|
|
||||||
// Show notification
|
|
||||||
prependNotification(
|
|
||||||
{
|
|
||||||
title: (
|
|
||||||
<Title order={4} className="text-overflow-1-line">
|
|
||||||
{data.data?.title}
|
|
||||||
</Title>
|
|
||||||
),
|
|
||||||
message: (
|
|
||||||
<Box>
|
|
||||||
<Text style={{ fontSize: 14 }} mb={2}>
|
|
||||||
Price: <span className="bold-text">${data.data?.price}</span>,
|
|
||||||
Site: <span className="bold-text">{data.data?.from_site}</span>,
|
|
||||||
Seller: <span className="bold-text">{data.data?.seller}</span>
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text style={{ fontSize: 12 }}>
|
|
||||||
{moment(new Date()).format('HH:mm A')}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
),
|
|
||||||
autoClose: isHotItem(data.data) ? false : timeout,
|
|
||||||
color: 'orange'
|
|
||||||
},
|
|
||||||
() => isHotItem(data.data)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Play sound effect
|
// Play sound effect
|
||||||
playNotificationSound()
|
playNotificationSound()
|
||||||
|
|
||||||
|
window.electron.ipcRenderer.invoke('show-window')
|
||||||
|
|
||||||
|
let timeoutId: NodeJS.Timeout | null = null
|
||||||
|
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isHotItem(data.data)) return
|
||||||
|
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
window.electron.ipcRenderer.invoke('hide-window')
|
||||||
|
}, timeoutNoti)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,7 +86,7 @@ function App(): React.JSX.Element {
|
||||||
pusher.unsubscribe(PUSHER_CHANNEL)
|
pusher.unsubscribe(PUSHER_CHANNEL)
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [timeout, hotItem])
|
}, [timeoutNoti, hotItem])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// load timeout config form local
|
// load timeout config form local
|
||||||
|
|
|
||||||
|
|
@ -41,11 +41,6 @@ export default function ConfigModal(props: IConfigModalProps) {
|
||||||
try {
|
try {
|
||||||
const data = await window.electron.ipcRenderer.invoke('get-config-file')
|
const data = await window.electron.ipcRenderer.invoke('get-config-file')
|
||||||
|
|
||||||
console.log(
|
|
||||||
'%csrc/renderer/src/components/ConfigModal.tsx:44 data',
|
|
||||||
'color: #007acc;',
|
|
||||||
data
|
|
||||||
)
|
|
||||||
if (!data) return
|
if (!data) return
|
||||||
|
|
||||||
form.setValues({ timeout: data?.noti_timeout })
|
form.setValues({ timeout: data?.noti_timeout })
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,3 @@
|
||||||
// /* eslint-disable @typescript-eslint/explicit-function-return-type */
|
|
||||||
// import { createNotificationsStore, NotificationData } from '@mantine/notifications'
|
|
||||||
// import notifySound from '../assets/notifty.mp3'
|
|
||||||
|
|
||||||
// export const myNotificationStore = createNotificationsStore()
|
|
||||||
|
|
||||||
// export function prependNotification(notification: NotificationData, callback?: () => boolean) {
|
|
||||||
// const id = notification.id ?? crypto.randomUUID()
|
|
||||||
// myNotificationStore.updateState((current) => {
|
|
||||||
// const existing = current.notifications.filter((n) => n.id !== id)
|
|
||||||
// return {
|
|
||||||
// ...current,
|
|
||||||
// notifications: [
|
|
||||||
// {
|
|
||||||
// ...notification,
|
|
||||||
// id
|
|
||||||
// },
|
|
||||||
// ...existing
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export const playNotificationSound = () => {
|
|
||||||
// const audio = new Audio(notifySound)
|
|
||||||
// audio.play().catch((e) => console.error('Audio play failed:', e))
|
|
||||||
// }
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
import { createNotificationsStore, NotificationData } from '@mantine/notifications'
|
import { createNotificationsStore, NotificationData } from '@mantine/notifications'
|
||||||
import notifySound from '../assets/notifty.mp3'
|
import notifySound from '../assets/notifty.mp3'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue