├── 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();
--------------------------------------------------------------------------------