├── assets └── icon.png ├── config.json ├── package.json ├── README.md ├── renderer.js ├── settings.html └── main.js /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vsec7/crypto-ticker-bar/main/assets/icon.png -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "tickers": [ 3 | "bitcoin", 4 | "ethereum", 5 | "solana" 6 | ] 7 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crypto-ticker", 3 | "version": "1.0.0", 4 | "main": "main.js", 5 | "scripts": { 6 | "start": "electron .", 7 | "build": "electron-builder" 8 | }, 9 | "build": { 10 | "appId": "com.ticker.crypto", 11 | "mac": { 12 | "category": "public.app-category.utilities", 13 | "target": ["dmg", "zip"] 14 | } 15 | }, 16 | "keywords": [], 17 | "author": "vsec7", 18 | "license": "ISC", 19 | "description": "", 20 | "dependencies": { 21 | "axios": "^1.10.0" 22 | }, 23 | "devDependencies": { 24 | "electron": "^37.2.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crypto Ticker Bar 2 | 3 | A simple Electron application that displays cryptocurrency prices bar for macos. 4 | 5 | ## Running the Application 6 | 7 | To run the application in development mode, follow these steps: 8 | 9 | 1. **Install dependencies:** 10 | ```bash 11 | npm install 12 | ``` 13 | 14 | 2. **Run the application:** 15 | ```bash 16 | npm start 17 | ``` 18 | 19 | ## Building the Application 20 | 21 | To build the application for production, first install `electron-builder`: 22 | 23 | ```bash 24 | npm install -g electron-builder 25 | ``` 26 | 27 | Then, run the following command: 28 | 29 | ```bash 30 | npm run build 31 | ``` 32 | 33 | This will create a distributable version of the application in the `dist/` directory. 34 | 35 | 36 | Created by github.com/vsec7 | viloid.sol -------------------------------------------------------------------------------- /renderer.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer } = require('electron'); 2 | 3 | const tickersList = document.getElementById('tickers-list'); 4 | const newTickerInput = document.getElementById('new-ticker'); 5 | const addTickerBtn = document.getElementById('add-ticker'); 6 | const fetchToggle = document.getElementById('fetch-toggle'); 7 | 8 | const renderTickers = (tickers) => { 9 | tickersList.innerHTML = ''; 10 | tickers.forEach(ticker => { 11 | const li = document.createElement('li'); 12 | li.textContent = ticker; 13 | const removeBtn = document.createElement('button'); 14 | removeBtn.textContent = 'Remove'; 15 | removeBtn.classList.add('remove-btn'); 16 | removeBtn.addEventListener('click', () => { 17 | ipcRenderer.send('remove-ticker', ticker); 18 | }); 19 | li.appendChild(removeBtn); 20 | tickersList.appendChild(li); 21 | }); 22 | }; 23 | 24 | addTickerBtn.addEventListener('click', () => { 25 | const newTicker = newTickerInput.value.trim(); 26 | if (newTicker) { 27 | ipcRenderer.send('add-ticker', newTicker); 28 | newTickerInput.value = ''; 29 | } 30 | }); 31 | 32 | fetchToggle.addEventListener('change', () => { 33 | ipcRenderer.send('toggle-fetching', fetchToggle.checked); 34 | }); 35 | 36 | 37 | ipcRenderer.on('is-fetching', (event, isFetching) => { 38 | fetchToggle.checked = isFetching; 39 | }); 40 | 41 | ipcRenderer.on('update-tickers', (event, tickers) => { 42 | renderTickers(tickers); 43 | }); 44 | 45 | ipcRenderer.send('get-tickers'); -------------------------------------------------------------------------------- /settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Crypto Ticker Settings 5 | 71 | 72 | 73 |

Settings

74 |
75 | 76 | 77 |
78 |

Tracked Coins

79 | 80 |

Add New Coin

81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const { app, Tray, Menu, shell, BrowserWindow, ipcMain } = require('electron'); 2 | const axios = require('axios'); 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | 6 | let tray = null; 7 | let settingsWindow = null; 8 | const configPath = path.join(__dirname, 'config.json'); 9 | let prices = []; 10 | let currentPriceIndex = 0; 11 | let isFetching = false; 12 | let fetchInterval = null; 13 | let updateInterval = null; 14 | 15 | const loadConfig = () => { 16 | if (fs.existsSync(configPath)) { 17 | return JSON.parse(fs.readFileSync(configPath, 'utf-8')); 18 | } 19 | const defaultConfig = { tickers: ['bitcoin', 'ethereum'] }; 20 | fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2)); 21 | return defaultConfig; 22 | }; 23 | 24 | const saveConfig = (config) => { 25 | fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); 26 | fetchPrices(); 27 | if (settingsWindow) { 28 | settingsWindow.webContents.send('update-tickers', config.tickers); 29 | } 30 | }; 31 | 32 | const fetchPrices = async () => { 33 | const config = loadConfig(); 34 | const ids = config.tickers.join(','); 35 | try { 36 | const response = await axios.get(`https://api.coingecko.com/api/v3/simple/price?ids=${ids}&vs_currencies=usd`); 37 | prices = config.tickers.map(ticker => { 38 | const price = response.data[ticker] ? response.data[ticker].usd : 'N/A'; 39 | return `${ticker.toUpperCase()}: $${price}`; 40 | }); 41 | } catch (error) { 42 | console.error('Error fetching prices:', error); 43 | prices = ['Error']; 44 | } 45 | }; 46 | 47 | const updateTrayTitle = () => { 48 | if (tray && prices.length > 0) { 49 | tray.setTitle(prices[currentPriceIndex]); 50 | currentPriceIndex = (currentPriceIndex + 1) % prices.length; 51 | } 52 | }; 53 | 54 | const startFetching = () => { 55 | if (!isFetching) return; 56 | fetchPrices(); 57 | fetchInterval = setInterval(fetchPrices, 60000); 58 | updateInterval = setInterval(updateTrayTitle, 5000); 59 | }; 60 | 61 | const stopFetching = () => { 62 | clearInterval(fetchInterval); 63 | clearInterval(updateInterval); 64 | tray.setTitle(''); 65 | }; 66 | 67 | const createSettingsWindow = () => { 68 | settingsWindow = new BrowserWindow({ 69 | width: 400, 70 | height: 500, 71 | webPreferences: { 72 | nodeIntegration: true, 73 | contextIsolation: false 74 | } 75 | }); 76 | 77 | settingsWindow.loadFile('settings.html'); 78 | 79 | settingsWindow.webContents.on('did-finish-load', () => { 80 | settingsWindow.webContents.send('is-fetching', isFetching); 81 | }); 82 | 83 | settingsWindow.on('closed', () => { 84 | settingsWindow = null; 85 | }); 86 | }; 87 | 88 | app.on('ready', () => { 89 | tray = new Tray(path.join(__dirname, 'assets', 'icon.png')); 90 | const contextMenu = Menu.buildFromTemplate([ 91 | { 92 | label: 'Active', 93 | type: 'checkbox', 94 | checked: isFetching, 95 | click: () => { 96 | isFetching = !isFetching; 97 | if (settingsWindow) { 98 | settingsWindow.webContents.send('is-fetching', isFetching); 99 | } 100 | if (isFetching) { 101 | startFetching(); 102 | } else { 103 | stopFetching(); 104 | } 105 | } 106 | }, 107 | { label: 'Settings', click: createSettingsWindow }, 108 | { type: 'separator' }, 109 | { label: '2025 | created by github.com/vsec7', enabled: false }, 110 | { type: 'separator' }, 111 | { label: 'Quit', type: 'normal', click: () => app.quit() } 112 | ]); 113 | tray.setToolTip('Crypto Price Ticker'); 114 | tray.setContextMenu(contextMenu); 115 | 116 | startFetching(); 117 | }); 118 | 119 | app.on('window-all-closed', () => { 120 | if (process.platform !== 'darwin') { 121 | app.quit(); 122 | } 123 | }); 124 | 125 | ipcMain.on('get-tickers', (event) => { 126 | const config = loadConfig(); 127 | event.sender.send('update-tickers', config.tickers); 128 | }); 129 | 130 | ipcMain.on('add-ticker', (event, ticker) => { 131 | const config = loadConfig(); 132 | if (!config.tickers.includes(ticker)) { 133 | config.tickers.push(ticker); 134 | saveConfig(config); 135 | } 136 | }); 137 | 138 | ipcMain.on('remove-ticker', (event, ticker) => { 139 | let config = loadConfig(); 140 | config.tickers = config.tickers.filter(t => t !== ticker); 141 | saveConfig(config); 142 | }); 143 | 144 | ipcMain.on('toggle-fetching', (event, shouldFetch) => { 145 | isFetching = shouldFetch; 146 | if (isFetching) { 147 | startFetching(); 148 | } else { 149 | stopFetching(); 150 | } 151 | }); 152 | 153 | app.dock.hide(); --------------------------------------------------------------------------------