diff --git a/package-lock.json b/package-lock.json index a01e644..70cb38e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "shotcut-app", - "version": "1.0.5", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "shotcut-app", - "version": "1.0.5", + "version": "1.0.1", "hasInstallScript": true, "dependencies": { "@electron-toolkit/preload": "^3.0.1", @@ -21,6 +21,7 @@ "electron-updater": "^6.3.9", "moment": "^2.30.1", "pusher-js": "^8.4.0", + "uuid": "^13.0.0", "windows-shortcuts": "^0.1.6" }, "devDependencies": { @@ -10726,6 +10727,19 @@ "dev": true, "license": "MIT" }, + "node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 6fc1cf5..22b07f1 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "electron-updater": "^6.3.9", "moment": "^2.30.1", "pusher-js": "^8.4.0", + "uuid": "^13.0.0", "windows-shortcuts": "^0.1.6" }, "devDependencies": { diff --git a/src/main/index.ts b/src/main/index.ts index 66c0e61..09784a7 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,20 +1,114 @@ /* eslint-disable prettier/prettier */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { electronApp, optimizer } from '@electron-toolkit/utils' -import { app, ipcMain, Menu, Tray } from 'electron' +import { electronApp, is, optimizer } from '@electron-toolkit/utils' +import { app, BrowserWindow, globalShortcut, ipcMain, Menu, Tray, screen, shell } from 'electron' import fs from 'fs' -import path from 'path' +import path, { join } from 'path' import ws from 'windows-shortcuts' import icon from '../../resources/icon.png?asset' -import { registerGlobalShortcuts, unregisterGlobalShortcuts } from './shortcut' +import { registerGlobalShortcuts, unregisterGlobalShortcuts } from './shotcut' + +let mainWindow: null | BrowserWindow = null +// eslint-disable-next-line prefer-const +let isQuiting = false + +function createWindow(): void { + // Get Screen width, height + const width = 800 + const height = 600 + + mainWindow = new BrowserWindow({ + width, + height, + show: false, + autoHideMenuBar: true, + ...(process.platform === 'linux' ? { icon } : {}), + webPreferences: { + preload: join(__dirname, '../preload/index.js'), + sandbox: false + } + }) + + // Set App Show Position + mainWindow.setPosition(width / 2, height / 2) + + mainWindow.on('ready-to-show', () => { + mainWindow?.show() + + // 🚀 Mở DevTools khi sẵn sàng + mainWindow?.webContents.openDevTools({ mode: 'detach' }) + }) + + mainWindow.webContents.setWindowOpenHandler((details) => { + shell.openExternal(details.url) + return { action: 'deny' } + }) + + // Make the window always on top + mainWindow.setAlwaysOnTop(true, 'normal') + + // mainWindow.webContents.openDevTools() + + // Inspect element with shortcut + mainWindow.webContents.once('did-finish-load', () => { + const shortcut = 'Control+Shift+C' + + mainWindow?.on('focus', () => { + globalShortcut.register(shortcut, () => { + const pos = screen.getCursorScreenPoint() + mainWindow?.webContents.inspectElement(pos.x, pos.y) + mainWindow?.webContents.focus() // optional: refocus back + }) + }) + + mainWindow?.on('blur', () => { + globalShortcut.unregister(shortcut) + }) + + mainWindow?.on('closed', () => { + globalShortcut.unregister(shortcut) + }) + }) + + // HMR for renderer base on electron-vite cli. + // Load the remote URL for development or the local html file for production. + if (is.dev && process.env['ELECTRON_RENDERER_URL']) { + mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) + } else { + 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: () => { + if (mainWindow) { + if (mainWindow.isMinimized()) { + mainWindow.restore() + } + mainWindow.show() + mainWindow.focus() + } else { + createWindow() + } + } + }, { label: 'Quit', click: () => { + isQuiting = true app.quit() } } @@ -69,3 +163,57 @@ app.whenReady().then(() => { app.on('will-quit', () => { unregisterGlobalShortcuts() }) + +// Quit when all windows are closed, except on macOS. There, it's common +// for applications and their menu bar to stay active until the user quits +// explicitly with Cmd + Q. +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit() + } +}) + +// In this file you can include the rest of your app's specific main process +// code. You can also put them in separate files and require them here. +// Custom events +ipcMain.handle('save-config-file', async (_event, jsonContent: any) => { + // const filePath = path.join(__dirname, 'config-data.json') + const userDataPath = app.getPath('userData') + const filePath = path.join(userDataPath, 'config-data.json') + + try { + // 📦 Ghi dữ liệu mới + const newData = jsonContent + + fs.writeFileSync(filePath, JSON.stringify(newData, null, 2), 'utf-8') + + // Đăng ký lại global shortcuts + unregisterGlobalShortcuts() + registerGlobalShortcuts() + + return { success: true, path: filePath } + } catch (error: any) { + console.error('Error saving file:', error) + return { success: false, error: error.message } + } +}) + +ipcMain.handle('get-config-file', async () => { + const userDataPath = app.getPath('userData') + const filePath = path.join(userDataPath, 'config-data.json') + + try { + // 📂 Kiểm tra file có tồn tại không + if (!fs.existsSync(filePath)) { + return { success: false, error: 'Config file does not exist' } + } + + const rawData = fs.readFileSync(filePath, 'utf-8') + const fileData = JSON.parse(rawData) + + return fileData + } catch (error: any) { + console.error('Error reading config file:', error) + return null + } +}) diff --git a/src/main/shortcut.ts b/src/main/shortcut.ts deleted file mode 100644 index db3b62e..0000000 --- a/src/main/shortcut.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { globalShortcut, clipboard, shell } from 'electron' - -export interface IShortcut { - shortcut: string - links: string[] -} - -// Danh sách shortcut mẫu -const shortcuts: IShortcut[] = [ - { - links: [ - 'https://int.ipsupply.com.au/erptools/001_search-vpn?search=${query}', - 'https://www.ebay.com/sch/i.html?_nkw=${query}&_sop=15' - ], - shortcut: 'CommandOrControl+Shift+1' - }, - { - links: ['https://esearch.danielvu.com?keyword=${query}'], - shortcut: 'CommandOrControl+Shift+2' - } -] - -/** - * Mở tất cả link gắn với shortcut và thay ${query} bằng clipboard text - */ -const handleOpenBrowserByLink = (data: IShortcut) => { - const text = clipboard.readText().trim() - - if (!text) { - console.log(`[${new Date().toLocaleTimeString()}] Clipboard is empty.`) - return - } - - const query = encodeURIComponent(text) - - data.links.forEach((link) => { - const url = link.replace(/\$\{query\}/g, query) // Thay ${query} trong link - console.log(`[${new Date().toLocaleTimeString()}] Opening URL: ${url}`) - shell.openExternal(url) // Mở trong browser mặc định - }) -} - -/** - * Đăng ký tất cả global shortcut - */ -export function registerGlobalShortcuts() { - shortcuts.forEach((sc) => { - const ret = globalShortcut.register(sc.shortcut, () => { - console.log(`🚀 Triggered shortcut: ${sc.shortcut}`) - handleOpenBrowserByLink(sc) - }) - - if (!ret) { - console.log(`❌ Failed to register shortcut: ${sc.shortcut}`) - } else { - console.log(`✅ Registered shortcut: ${sc.shortcut}`) - } - }) -} - -/** - * Xóa tất cả global shortcut khi thoát app - */ -export function unregisterGlobalShortcuts() { - globalShortcut.unregisterAll() - console.log('ℹ️ All global shortcuts unregistered.') -} diff --git a/src/main/shotcut.ts b/src/main/shotcut.ts new file mode 100644 index 0000000..28818eb --- /dev/null +++ b/src/main/shotcut.ts @@ -0,0 +1,126 @@ +// shotcut.ts +import { globalShortcut, clipboard, shell, app } from 'electron' +import fs from 'fs' +import path from 'path' +import { v4 as uuid } from 'uuid' + +export interface IShortcut { + id: string + shortcut: string + links: string[] +} + +// Danh sách shortcut mặc định +export const defaultShortcuts: IShortcut[] = [ + { + id: uuid(), + links: [ + 'https://int.ipsupply.com.au/erptools/001_search-vpn?search=${keyword}', + 'https://www.ebay.com/sch/i.html?_nkw=${keyword}&_sop=15' + ], + shortcut: 'CommandOrControl+Shift+1' + }, + { + id: uuid(), + links: ['https://esearch.danielvu.com?keyword=${keyword}'], + shortcut: 'CommandOrControl+Shift+2' + } +] + +// Lấy đường dẫn file config trong thư mục userData +const getConfigFilePath = () => { + return path.join(app.getPath('userData'), 'config-data.json') +} + +/** + * Đảm bảo file config tồn tại + * Nếu chưa có, tạo mới với dữ liệu defaultShortcuts + */ +const ensureConfigFile = () => { + const filePath = getConfigFilePath() + + if (!fs.existsSync(filePath)) { + try { + fs.writeFileSync(filePath, JSON.stringify(defaultShortcuts, null, 2), 'utf-8') + console.log(`📝 Created default config file at: ${filePath}`) + } catch (err) { + console.error('❌ Failed to create default config file:', err) + } + } +} + +/** + * Đọc danh sách shortcut từ file config + */ +const loadShortcutsFromConfig = (): IShortcut[] => { + const filePath = getConfigFilePath() + + ensureConfigFile() // 🔹 Đảm bảo luôn có file trước khi đọc + + try { + const rawData = fs.readFileSync(filePath, 'utf-8') + const shortcuts = JSON.parse(rawData) as IShortcut[] + + if (!Array.isArray(shortcuts)) { + throw new Error('Config file format is invalid: must be an array') + } + + return shortcuts + } catch (err) { + console.error('❌ Failed to read or parse config file:', err) + return [] + } +} + +/** + * Mở tất cả link gắn với shortcut và thay ${query} bằng clipboard text + */ +const handleOpenBrowserByLink = (data: IShortcut) => { + const text = clipboard.readText().trim() + + if (!text) { + console.log(`[${new Date().toLocaleTimeString()}] Clipboard is empty.`) + return + } + + const query = encodeURIComponent(text) + + data.links.forEach((link) => { + const url = link.replace(/\$\{keyword\}/g, query) // Thay ${query} trong link + console.log(`[${new Date().toLocaleTimeString()}] Opening URL: ${url}`) + shell.openExternal(url) // Mở trong browser mặc định + }) +} + +/** + * Đăng ký tất cả global shortcut từ file config + */ +export function registerGlobalShortcuts() { + const shortcuts = loadShortcutsFromConfig() + + if (shortcuts.length === 0) { + console.log('⚠️ No shortcuts found to register.') + return + } + + shortcuts.forEach((sc) => { + const ret = globalShortcut.register(sc.shortcut, () => { + console.log(`🚀 Triggered shortcut: ${sc.shortcut}`) + handleOpenBrowserByLink(sc) + }) + + if (!ret) { + console.log(`❌ Failed to register shortcut: ${sc.shortcut}`) + } else { + console.log(`✅ Registered shortcut: ${sc.shortcut}`) + } + }) +} + +/** + * Xóa tất cả global shortcut khi thoát app + */ +export function unregisterGlobalShortcuts() { + globalShortcut.unregisterAll() + console.log('ℹ️ All global shortcuts unregistered.') +} diff --git a/src/renderer/index.html b/src/renderer/index.html index e69de29..9e9fc64 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -0,0 +1,17 @@ + + + + + Shotcut + + + + + +
+ + + diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx new file mode 100644 index 0000000..dffae70 --- /dev/null +++ b/src/renderer/src/App.tsx @@ -0,0 +1,92 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Box, Container, LoadingOverlay, MantineProvider } from '@mantine/core' +import '@mantine/core/styles.css' +import '@mantine/notifications/styles.css' +import { Suspense, useEffect, useState } from 'react' +import { ShotcutForm } from './components/ShotcutForm' +import { ShotcutList } from './components/ShotcutList' +import { theme } from './theme' +import { IShotcut } from './types' +import { getConfigFile, saveConfigFile } from './api/config' + +function App(): React.JSX.Element { + const [loading, setLoading] = useState(true) + const [shotcuts, setShotcuts] = useState([]) + + // Hàm lưu config + const saveConfig = async (newShortcuts: IShotcut[]) => { + const result = await saveConfigFile(newShortcuts) + if (!result.success) { + console.error('Failed to save config:', result.error) + } else { + console.log('✅ Config saved to', result.path) + } + } + + const handleDelete = (shotcut: IShotcut) => { + const newShortcuts = shotcuts.filter((item) => item.id !== shotcut.id) + + setShotcuts(newShortcuts) + saveConfig(newShortcuts) + } + + // Gọi IPC để lấy dữ liệu config + useEffect(() => { + const fetchData = async () => { + setLoading(true) + try { + const data = await getConfigFile() + console.log('Config data:', data) + + if (data) { + setShotcuts(data) + } else { + setShotcuts([]) + } + } catch (error) { + console.error('Error loading config file:', error) + setShotcuts([]) + } finally { + setLoading(false) + } + } + + fetchData() + }, []) + + return ( + + + } + > + + { + const updated = [newItem, ...shotcuts] + setShotcuts(updated) + saveConfig(updated) + }} + /> + + + + + + + + + + ) +} + +export default App diff --git a/src/renderer/src/api/config.ts b/src/renderer/src/api/config.ts new file mode 100644 index 0000000..a74514a --- /dev/null +++ b/src/renderer/src/api/config.ts @@ -0,0 +1,10 @@ +import { IShotcut } from '@renderer/types' + +// src/api/config.ts +export const getConfigFile = async () => { + return await window.electron.ipcRenderer.invoke('get-config-file') +} + +export const saveConfigFile = async (data: IShotcut[]) => { + return await window.electron.ipcRenderer.invoke('save-config-file', data) +} diff --git a/src/renderer/src/assets/main.css b/src/renderer/src/assets/main.css new file mode 100644 index 0000000..24d0cc9 --- /dev/null +++ b/src/renderer/src/assets/main.css @@ -0,0 +1,54 @@ +@keyframes borderPulse { + 0% { + border-color: #e03131; + } + 50% { + border-color: black; + } + 100% { + border-color: #e03131; + } +} + +.item-card { + animation: borderPulse 1s infinite; + border: 4px solid #e03131; + border-radius: 4px; + transition: border-color 0.3s ease; + padding: 10px; +} + +.bold-text { + font-weight: 600; + color: #495057; +} + +.item-card p { + font-size: 14px !important; +} + +.text-overflow-1-line { + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.text-overflow-1-line:hover { + text-decoration: underline; + cursor: pointer; +} + +.item-picture { + height: auto; + width: 100%; + max-height: 100px; + border-radius: 4px; + object-fit: contain; +} + +@media only screen and (max-width: 768px) { + .item-picture { + object-fit: cover; + } +} diff --git a/src/renderer/src/components/ConfigModal.tsx b/src/renderer/src/components/ConfigModal.tsx new file mode 100644 index 0000000..cbe6b44 --- /dev/null +++ b/src/renderer/src/components/ConfigModal.tsx @@ -0,0 +1,89 @@ +import { Modal, ModalProps, NumberInput, Button, Stack } from '@mantine/core' +import { useForm } from '@mantine/form' +import { useEffect } from 'react' + +export interface IConfigModalProps extends ModalProps { + onSaved?: () => void +} + +export default function ConfigModal(props: IConfigModalProps) { + const form = useForm({ + initialValues: { + timeout: 10, // default timeout 30s + delay_hide: 10 + }, + validate: { + timeout: (value) => (value <= 0 ? 'Timeout must be greater than 0' : null), + delay_hide: (value) => (value <= 0 ? 'Delay must be greater than 0' : null) + } + }) + + const handleSave = async () => { + if (!form.isValid()) return // Nếu form lỗi thì không save + + const jsonContent = { + noti_timeout: form.values.timeout, + delay_hide: form.values.delay_hide + } + + try { + const result = await window.electron.ipcRenderer.invoke('save-config-file', jsonContent) + if (result.success) { + props.onClose?.() + + props.onSaved?.() + } else { + console.error('Failed to save file:', result.error) + } + } catch (error) { + console.error('IPC error:', error) + } + } + + const loadConfigData = async () => { + try { + const data = await window.electron.ipcRenderer.invoke('get-config-file') + + if (!data) return + + form.setValues({ timeout: data?.noti_timeout, delay_hide: data.delay_hide }) + } catch (error) { + console.log( + '%csrc/renderer/src/components/ConfigModal.tsx:41 error', + 'color: #007acc;', + error + ) + } + } + + useEffect(() => { + if (props.opened) { + loadConfigData() + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [props.opened]) + return ( + +
+ + + + + + +
+
+ ) +} diff --git a/src/renderer/src/components/ShotcutForm.tsx b/src/renderer/src/components/ShotcutForm.tsx new file mode 100644 index 0000000..7c765d1 --- /dev/null +++ b/src/renderer/src/components/ShotcutForm.tsx @@ -0,0 +1,127 @@ +'use client' + +import { ActionIcon, Button, Code, Group, Paper, Stack, Text, TextInput } from '@mantine/core' +import { useForm, zodResolver } from '@mantine/form' +import { IShotcut } from '@renderer/types' +import { IconPlus, IconX } from '@tabler/icons-react' +import { v4 as uuid } from 'uuid' +import * as z from 'zod' + +// Modifier keys +const MODIFIERS = ['Ctrl', 'Control', 'Cmd', 'Command', 'Alt', 'Option', 'Shift'] +// Last key regex +const keyRegex = /^([A-Z0-9]|F[1-9]|F1[0-2]|Enter|Tab)$/i + +// Zod schema +const ShotcutSchema = z.object({ + shortcut: z + .string() + .nonempty('Shortcut is required') + .refine((val) => { + const parts = val.split('+').map((p) => p.trim()) + if (parts.length < 2) return false + const last = parts[parts.length - 1] + const modifiers = parts.slice(0, -1) + return modifiers.every((m) => MODIFIERS.includes(m)) && keyRegex.test(last) + }, 'Invalid shortcut format. Example: Ctrl+Shift+1'), + links: z + .array( + z + .string() + .nonempty('Link cannot be empty') + .refine((val) => { + const temp = val.replaceAll('{{keyword}}', 'test') + try { + new URL(temp) + return true + } catch { + return false + } + }, 'Invalid URL. Can include {{keyword}}') + ) + .min(1, 'At least one link is required') +}) + +type ShotcutFormValues = z.infer + +interface ShotcutFormProps { + onSubmit: (shotcut: IShotcut) => void +} + +export function ShotcutForm({ onSubmit }: ShotcutFormProps) { + const form = useForm({ + initialValues: { shortcut: '', links: [''] }, + validate: zodResolver(ShotcutSchema) + }) + + const addLink = () => form.insertListItem('links', '') + const removeLink = (index: number) => form.removeListItem('links', index) + + const handleSubmit = (values: ShotcutFormValues) => { + onSubmit({ ...values, id: uuid() }) + form.reset() + } + + return ( + +
+ + + Create New Shortcut + + + + + + + Links + + Use {'{keyword}'} as placeholder + + + + {form.values.links.map((link, index) => ( + + + {form.values.links.length > 1 && ( + removeLink(index)} + > + + + )} + + ))} + + + + + + +
+
+ ) +} diff --git a/src/renderer/src/components/ShotcutList.tsx b/src/renderer/src/components/ShotcutList.tsx new file mode 100644 index 0000000..7831b0e --- /dev/null +++ b/src/renderer/src/components/ShotcutList.tsx @@ -0,0 +1,105 @@ +'use client' + +import { useState } from 'react' +import { ActionIcon, Badge, Card, Group, Stack, Text, Modal, Button, Flex } from '@mantine/core' +import { IShotcut } from '@renderer/types' +import { IconExternalLink, IconTrash } from '@tabler/icons-react' + +interface ShotcutListProps { + shotcuts: IShotcut[] + onDelete: (shotcut: IShotcut) => void +} + +export function ShotcutList({ shotcuts, onDelete }: ShotcutListProps) { + const [opened, setOpened] = useState(false) + const [selectedShotcut, setSelectedShotcut] = useState(null) + + const openConfirmModal = (shotcut: IShotcut) => { + setSelectedShotcut(shotcut) + setOpened(true) + } + + const handleConfirmDelete = () => { + if (selectedShotcut) { + onDelete(selectedShotcut) + setSelectedShotcut(null) + setOpened(false) + } + } + + if (shotcuts.length === 0) { + return ( + + + No shortcuts yet. Create your first shortcut! + + + ) + } + + return ( + + + Shortcut List + + + {shotcuts.map((shotcut, index) => ( + + {/* Header */} + + + {shotcut.shortcut} + + openConfirmModal(shotcut)} + title="Delete shortcut" + > + + + + + {/* Content */} + + + {shotcut.links.length} link(s) + + + {/* Link badges */} + + {shotcut.links.map((link, linkIndex) => ( + window.open(link, '_blank')} + leftSection={} + > + {link} + + ))} + + + + ))} + + {/* Confirm Modal */} + setOpened(false)} title="Confirm Delete" centered> + + Are you sure you want to delete shortcut{' '} + {selectedShotcut?.shortcut}? + + + + + + + + ) +} diff --git a/src/renderer/src/env.d.ts b/src/renderer/src/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/renderer/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/renderer/src/global.d.ts b/src/renderer/src/global.d.ts new file mode 100644 index 0000000..192cafb --- /dev/null +++ b/src/renderer/src/global.d.ts @@ -0,0 +1,7 @@ +export {} + +declare global { + interface Window { + disableSound: any + } +} diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx new file mode 100644 index 0000000..686e520 --- /dev/null +++ b/src/renderer/src/main.tsx @@ -0,0 +1,11 @@ +import './assets/main.css' + +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import App from './App' + +createRoot(document.getElementById('root')!).render( + + + +) diff --git a/src/renderer/src/pusher.ts b/src/renderer/src/pusher.ts new file mode 100644 index 0000000..d6fc5cc --- /dev/null +++ b/src/renderer/src/pusher.ts @@ -0,0 +1,6 @@ +import Pusher from 'pusher-js' + +export const pusher = new Pusher(import.meta.env.VITE_PUSHER_APP_KEY, { + cluster: 'ap4' + // encrypted: true +}) diff --git a/src/renderer/src/theme.ts b/src/renderer/src/theme.ts new file mode 100644 index 0000000..c382ff3 --- /dev/null +++ b/src/renderer/src/theme.ts @@ -0,0 +1,5 @@ +import { createTheme } from '@mantine/core' +import { themeToVars } from '@mantine/vanilla-extract' + +export const theme = createTheme({}) +export const vars = themeToVars(theme) diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts new file mode 100644 index 0000000..52497b4 --- /dev/null +++ b/src/renderer/src/types/index.ts @@ -0,0 +1,5 @@ +export interface IShotcut { + id: string + shortcut: string + links: string[] +} diff --git a/src/renderer/src/utils/fn.ts b/src/renderer/src/utils/fn.ts new file mode 100644 index 0000000..f4ee9f5 --- /dev/null +++ b/src/renderer/src/utils/fn.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +export const linkToItem = (item: any) => { + switch (item.from_site) { + case 'EBAY_AU': + return `https://www.ebay.com.au/itm/${item.id}` + case 'EBAY_ENCA': + return `https://www.ebay.ca/itm/${item.id}` + case 'EBAY_GB': + return `https://www.ebay.co.uk/itm/${item.id}` + default: + return `https://www.ebay.com/itm/${item.id}` + } +}