229 |
230 | ${iconHtml} ${stat.tag.name}
231 |
232 |
233 | ${this.formatDuration(stat.duration)}
234 | ${stat.percentage.toFixed(1)}%
235 |
236 |
237 | `;
238 |
239 | legendContainer.appendChild(legendItem);
240 | });
241 |
242 | // If there are more tags, show a summary
243 | if (remainingStats.length > 0) {
244 | const remainingDuration = remainingStats.reduce((sum, stat) => sum + stat.duration, 0);
245 | const remainingPercentage = remainingStats.reduce((sum, stat) => sum + stat.percentage, 0);
246 |
247 | const othersItem = document.createElement('div');
248 | othersItem.className = 'tag-legend-item tag-legend-others';
249 |
250 | othersItem.innerHTML = `
251 |
253 |
254 | ${remainingStats.length} others
255 |
256 |
257 | ${this.formatDuration(remainingDuration)}
258 | ${remainingPercentage.toFixed(1)}%
259 |
260 |
261 | `;
262 |
263 | legendContainer.appendChild(othersItem);
264 | }
265 | }
266 | }
--------------------------------------------------------------------------------
/src/utils/theme-loader.js:
--------------------------------------------------------------------------------
1 | // Auto Theme Loader
2 | // This utility automatically discovers and loads all theme CSS files from the themes folder
3 |
4 | class ThemeLoader {
5 | constructor() {
6 | this.loadedThemes = new Set();
7 | this.themeStyles = new Map();
8 | }
9 |
10 | /**
11 | * Automatically discover and load all theme CSS files
12 | */
13 | async loadAllThemes() {
14 | try {
15 | // Get all CSS files from the themes directory
16 | const themeFiles = await this.discoverThemeFiles();
17 |
18 | console.log(`🎨 Discovered ${themeFiles.length} theme files:`, themeFiles);
19 |
20 | // Load each theme file
21 | for (const themeFile of themeFiles) {
22 | await this.loadThemeFile(themeFile);
23 | }
24 |
25 | console.log(`🎨 Auto-loaded ${this.loadedThemes.size} themes successfully`);
26 | return Array.from(this.loadedThemes);
27 | } catch (error) {
28 | console.error('❌ Failed to auto-load themes:', error);
29 | return [];
30 | }
31 | }
32 |
33 | /**
34 | * Discover all CSS files in the themes directory
35 | */
36 | async discoverThemeFiles() {
37 | // In a browser environment, we need to use a different approach
38 | // Since we can't directly read the filesystem, we'll use a predefined list
39 | // that gets updated by the build process or manually maintained
40 |
41 | // This could be enhanced to use a build-time script that generates this list
42 | const knownThemes = [
43 | 'espresso.css',
44 | 'pipboy.css',
45 | 'pommodore64.css'
46 | ];
47 |
48 | return knownThemes;
49 | }
50 |
51 | /**
52 | * Load a specific theme CSS file
53 | */
54 | async loadThemeFile(filename) {
55 | const themeId = filename.replace('.css', '');
56 |
57 | if (this.loadedThemes.has(themeId)) {
58 | console.log(`🎨 Theme ${themeId} already loaded, skipping`);
59 | return;
60 | }
61 |
62 | try {
63 | // Since CSS is already imported statically in main.css,
64 | // we just need to register the theme in our loaded themes
65 | console.log(`✅ Theme registered: ${themeId}`);
66 | this.loadedThemes.add(themeId);
67 |
68 | // Extract theme metadata from CSS file
69 | await this.extractThemeMetadata(themeId);
70 |
71 | } catch (error) {
72 | console.error(`❌ Error registering theme ${themeId}:`, error);
73 | }
74 | }
75 |
76 | /**
77 | * Extract theme metadata from CSS comments
78 | */
79 | async extractThemeMetadata(themeId) {
80 | try {
81 | // Fetch the CSS file to read metadata from comments
82 | const response = await fetch(`./src/styles/themes/${themeId}.css`);
83 | const cssContent = await response.text();
84 |
85 | // Parse metadata from CSS comments
86 | const metadata = this.parseThemeMetadata(cssContent);
87 |
88 | if (metadata) {
89 | // Add to TIMER_THEMES dynamically
90 | const { TIMER_THEMES } = await import('./timer-themes.js');
91 |
92 | if (!TIMER_THEMES[themeId]) {
93 | TIMER_THEMES[themeId] = {
94 | name: metadata.name || this.capitalizeFirst(themeId),
95 | description: metadata.description || `Auto-discovered theme: ${themeId}`,
96 | supports: metadata.supports || ['light', 'dark'],
97 | isDefault: false,
98 | preview: metadata.preview || {
99 | focus: '#e74c3c',
100 | break: '#2ecc71',
101 | longBreak: '#3498db'
102 | }
103 | };
104 |
105 | console.log(`📝 Auto-registered theme: ${themeId}`, TIMER_THEMES[themeId]);
106 | }
107 | }
108 | } catch (error) {
109 | console.warn(`⚠️ Could not extract metadata for theme ${themeId}:`, error);
110 | }
111 | }
112 |
113 | /**
114 | * Parse theme metadata from CSS comments
115 | */
116 | parseThemeMetadata(cssContent) {
117 | try {
118 | // Look for theme metadata in CSS comments
119 | const metadataRegex = /\/\*\s*Timer Theme:\s*(.+?)\s*\*\s*Author:\s*(.+?)\s*\*\s*Description:\s*(.+?)\s*\*\s*Supports:\s*(.+?)\s*\*\//s;
120 | const match = cssContent.match(metadataRegex);
121 |
122 | if (match) {
123 | const [, name, author, description, supports] = match;
124 |
125 | // Parse supports field
126 | const supportsModes = supports.toLowerCase().includes('light') && supports.toLowerCase().includes('dark')
127 | ? ['light', 'dark']
128 | : supports.toLowerCase().includes('dark')
129 | ? ['dark']
130 | : ['light'];
131 |
132 | // Try to extract color values for preview
133 | const preview = this.extractPreviewColors(cssContent);
134 |
135 | return {
136 | name: name.trim(),
137 | author: author.trim(),
138 | description: description.trim(),
139 | supports: supportsModes,
140 | preview
141 | };
142 | }
143 | } catch (error) {
144 | console.warn('Could not parse theme metadata:', error);
145 | }
146 |
147 | return null;
148 | }
149 |
150 | /**
151 | * Extract preview colors from CSS variables
152 | */
153 | extractPreviewColors(cssContent) {
154 | const colors = {
155 | focus: '#e74c3c',
156 | break: '#2ecc71',
157 | longBreak: '#3498db'
158 | };
159 |
160 | try {
161 | // Extract --focus-color, --break-color, --long-break-color
162 | const focusMatch = cssContent.match(/--focus-color:\s*([^;]+);/);
163 | const breakMatch = cssContent.match(/--break-color:\s*([^;]+);/);
164 | const longBreakMatch = cssContent.match(/--long-break-color:\s*([^;]+);/);
165 |
166 | if (focusMatch) colors.focus = focusMatch[1].trim();
167 | if (breakMatch) colors.break = breakMatch[1].trim();
168 | if (longBreakMatch) colors.longBreak = longBreakMatch[1].trim();
169 | } catch (error) {
170 | console.warn('Could not extract preview colors:', error);
171 | }
172 |
173 | return colors;
174 | }
175 |
176 | /**
177 | * Capitalize first letter of a string
178 | */
179 | capitalizeFirst(str) {
180 | return str.charAt(0).toUpperCase() + str.slice(1);
181 | }
182 |
183 | /**
184 | * Remove a loaded theme
185 | */
186 | unloadTheme(themeId) {
187 | const linkElement = this.themeStyles.get(themeId);
188 | if (linkElement) {
189 | document.head.removeChild(linkElement);
190 | this.loadedThemes.delete(themeId);
191 | this.themeStyles.delete(themeId);
192 | console.log(`🗑️ Unloaded theme: ${themeId}`);
193 | }
194 | }
195 |
196 | /**
197 | * Get list of loaded themes
198 | */
199 | getLoadedThemes() {
200 | return Array.from(this.loadedThemes);
201 | }
202 |
203 | /**
204 | * Check if a theme is loaded
205 | */
206 | isThemeLoaded(themeId) {
207 | return this.loadedThemes.has(themeId);
208 | }
209 | }
210 |
211 | // Create and export a singleton instance
212 | export const themeLoader = new ThemeLoader();
213 |
214 | // Auto-load themes when this module is imported
215 | export async function initializeAutoThemeLoader() {
216 | console.log('🎨 Initializing auto theme loader...');
217 | const loadedThemes = await themeLoader.loadAllThemes();
218 | return loadedThemes;
219 | }
220 |
221 | export default themeLoader;
222 |
--------------------------------------------------------------------------------
/src/utils/timer-themes.js:
--------------------------------------------------------------------------------
1 | // Timer Themes Configuration
2 | // This file defines all available timer themes and their properties
3 |
4 | export const TIMER_THEMES = {
5 | espresso: {
6 | name: 'Espresso',
7 | description: 'Warm, coffee-inspired colors with rich earth tones',
8 | supports: ['light', 'dark'],
9 | isDefault: true,
10 | preview: {
11 | focus: '#e74c3c',
12 | break: '#2ecc71',
13 | longBreak: '#3498db'
14 | }
15 | },
16 | pommodore64: {
17 | name: 'Pommodore64',
18 | description: 'Un tema retrò ispirato al Commodore 64 con colori nostalgici e font pixelato',
19 | supports: ['light'],
20 | isDefault: false,
21 | preview: {
22 | focus: '#6c5ce7',
23 | break: '#0984e3',
24 | longBreak: '#00b894'
25 | }
26 | },
27 | pipboy: {
28 | name: 'PipBoy',
29 | description: 'A retro-futuristic theme inspired by Fallout\'s PipBoy interface with green terminal colors and digital effects',
30 | supports: ['dark'],
31 | isDefault: false,
32 | preview: {
33 | focus: '#00ff41',
34 | break: '#39ff14',
35 | longBreak: '#00cc33'
36 | }
37 | },
38 | };
39 |
40 | // Function to dynamically register a new theme
41 | export function registerTheme(themeId, themeConfig) {
42 | if (TIMER_THEMES[themeId]) {
43 | console.warn(`🎨 Theme ${themeId} already exists, overriding...`);
44 | }
45 |
46 | TIMER_THEMES[themeId] = {
47 | name: themeConfig.name || themeId,
48 | description: themeConfig.description || `Theme: ${themeId}`,
49 | supports: themeConfig.supports || ['light', 'dark'],
50 | isDefault: themeConfig.isDefault || false,
51 | preview: themeConfig.preview || {
52 | focus: '#e74c3c',
53 | break: '#2ecc71',
54 | longBreak: '#3498db'
55 | }
56 | };
57 |
58 | console.log(`✅ Registered theme: ${themeId}`, TIMER_THEMES[themeId]);
59 | return TIMER_THEMES[themeId];
60 | }
61 |
62 | // Function to unregister a theme
63 | export function unregisterTheme(themeId) {
64 | if (TIMER_THEMES[themeId] && !TIMER_THEMES[themeId].isDefault) {
65 | delete TIMER_THEMES[themeId];
66 | console.log(`🗑️ Unregistered theme: ${themeId}`);
67 | return true;
68 | }
69 | return false;
70 | }
71 |
72 | // Get theme by ID
73 | export function getThemeById(themeId) {
74 | return TIMER_THEMES[themeId] || TIMER_THEMES.espresso;
75 | }
76 |
77 | // Get all available themes
78 | export function getAllThemes() {
79 | return Object.entries(TIMER_THEMES).map(([id, theme]) => ({
80 | id,
81 | ...theme
82 | }));
83 | }
84 |
85 | // Get compatible themes for current color mode
86 | export function getCompatibleThemes(colorMode = 'light') {
87 | return getAllThemes().filter(theme =>
88 | theme.supports.includes(colorMode)
89 | );
90 | }
91 |
92 | // Check if theme is compatible with color mode
93 | export function isThemeCompatible(themeId, colorMode = 'light') {
94 | const theme = getThemeById(themeId);
95 | return theme.supports.includes(colorMode);
96 | }
97 |
98 | // Get default theme
99 | export function getDefaultTheme() {
100 | const defaultTheme = getAllThemes().find(theme => theme.isDefault);
101 | return defaultTheme || getAllThemes()[0];
102 | }
103 |
--------------------------------------------------------------------------------
/src/version.js:
--------------------------------------------------------------------------------
1 | // Questo file viene generato automaticamente durante il build
2 | // Contiene la versione corrente dell'applicazione
3 | export const APP_VERSION = '0.4.4';
4 |
--------------------------------------------------------------------------------
/verify-updates.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Verification script for Tempo update system configuration
4 | # This script checks if everything is properly set up for automatic updates
5 |
6 | echo "🔍 Tempo Update System Verification"
7 | echo "=================================="
8 | echo ""
9 |
10 | # Colors for output
11 | RED='\033[0;31m'
12 | GREEN='\033[0;32m'
13 | YELLOW='\033[1;33m'
14 | BLUE='\033[0;34m'
15 | NC='\033[0m' # No Color
16 |
17 | # Function to print status
18 | print_status() {
19 | local status=$1
20 | local message=$2
21 | if [ "$status" = "ok" ]; then
22 | echo -e "${GREEN}✅${NC} $message"
23 | elif [ "$status" = "warning" ]; then
24 | echo -e "${YELLOW}⚠️${NC} $message"
25 | else
26 | echo -e "${RED}❌${NC} $message"
27 | fi
28 | }
29 |
30 | # Function to check if a file exists
31 | check_file() {
32 | local file=$1
33 | local description=$2
34 | if [ -f "$file" ]; then
35 | print_status "ok" "$description found"
36 | return 0
37 | else
38 | print_status "error" "$description not found at $file"
39 | return 1
40 | fi
41 | }
42 |
43 | # Function to check if a command exists
44 | check_command() {
45 | local cmd=$1
46 | local description=$2
47 | if command -v "$cmd" &> /dev/null; then
48 | print_status "ok" "$description available"
49 | return 0
50 | else
51 | print_status "error" "$description not found"
52 | return 1
53 | fi
54 | }
55 |
56 | echo "📋 Checking Dependencies"
57 | echo "------------------------"
58 |
59 | # Check for required commands
60 | check_command "npm" "npm"
61 | check_command "npx" "npx"
62 | check_command "git" "git"
63 |
64 | # Check if tauri CLI is available
65 | if npx tauri --version &> /dev/null; then
66 | print_status "ok" "Tauri CLI available"
67 | else
68 | print_status "error" "Tauri CLI not available"
69 | fi
70 |
71 | echo ""
72 | echo "📁 Checking Project Files"
73 | echo "-------------------------"
74 |
75 | # Check critical files
76 | check_file "src-tauri/tauri.conf.json" "Tauri configuration"
77 | check_file ".github/workflows/release.yml" "GitHub Actions workflow"
78 | check_file "src-tauri/capabilities/default.json" "Tauri capabilities"
79 | check_file "src/managers/update-manager.js" "Update manager"
80 | check_file "src/components/update-notification.js" "Update notification component"
81 |
82 | echo ""
83 | echo "🔧 Checking Configuration"
84 | echo "-------------------------"
85 |
86 | # Check tauri.conf.json for updater configuration
87 | if [ -f "src-tauri/tauri.conf.json" ]; then
88 | if grep -q '"updater"' src-tauri/tauri.conf.json; then
89 | print_status "ok" "Updater plugin configured in tauri.conf.json"
90 |
91 | # Check if pubkey is set
92 | if grep -q '"pubkey": ""' src-tauri/tauri.conf.json; then
93 | print_status "warning" "Public key is empty in tauri.conf.json"
94 | elif grep -q '"pubkey":' src-tauri/tauri.conf.json; then
95 | print_status "ok" "Public key is set in tauri.conf.json"
96 | fi
97 |
98 | # Check endpoint
99 | if grep -q 'YOUR_USERNAME' src-tauri/tauri.conf.json; then
100 | print_status "warning" "GitHub repository placeholders not replaced"
101 | elif grep -q 'github.com' src-tauri/tauri.conf.json; then
102 | print_status "ok" "GitHub endpoint configured"
103 | fi
104 | else
105 | print_status "error" "Updater plugin not configured in tauri.conf.json"
106 | fi
107 | fi
108 |
109 | # Check capabilities
110 | if [ -f "src-tauri/capabilities/default.json" ]; then
111 | if grep -q 'updater:allow-check' src-tauri/capabilities/default.json; then
112 | print_status "ok" "Updater permissions configured"
113 | else
114 | print_status "error" "Updater permissions missing in capabilities"
115 | fi
116 | fi
117 |
118 | echo ""
119 | echo "🔑 Checking Signing Keys"
120 | echo "------------------------"
121 |
122 | # Check for signing keys
123 | KEY_PATH="$HOME/.tauri/tempo_signing_key"
124 | PUB_KEY_PATH="$HOME/.tauri/tempo_signing_key.pub"
125 |
126 | if [ -f "$KEY_PATH" ]; then
127 | print_status "ok" "Private signing key found"
128 | else
129 | print_status "error" "Private signing key not found at $KEY_PATH"
130 | fi
131 |
132 | if [ -f "$PUB_KEY_PATH" ]; then
133 | print_status "ok" "Public signing key found"
134 | echo -e "${BLUE}📄 Public key content:${NC}"
135 | cat "$PUB_KEY_PATH"
136 | echo ""
137 | else
138 | print_status "error" "Public signing key not found at $PUB_KEY_PATH"
139 | fi
140 |
141 | echo ""
142 | echo "📦 Checking Dependencies"
143 | echo "------------------------"
144 |
145 | # Check package.json for required dependencies
146 | if [ -f "package.json" ]; then
147 | if grep -q '@tauri-apps/plugin-updater' package.json; then
148 | print_status "ok" "Updater plugin dependency found"
149 | else
150 | print_status "warning" "Updater plugin dependency not found in package.json"
151 | fi
152 |
153 | if grep -q '@tauri-apps/plugin-dialog' package.json; then
154 | print_status "ok" "Dialog plugin dependency found"
155 | else
156 | print_status "warning" "Dialog plugin dependency not found in package.json"
157 | fi
158 | fi
159 |
160 | echo ""
161 | echo "🚀 Next Steps"
162 | echo "-------------"
163 |
164 | if [ -f "$KEY_PATH" ] && [ -f "$PUB_KEY_PATH" ]; then
165 | echo -e "${GREEN}1.${NC} Add these secrets to your GitHub repository:"
166 | echo " - TAURI_SIGNING_PRIVATE_KEY: (content of $KEY_PATH)"
167 | echo " - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: (your key password)"
168 | echo ""
169 | fi
170 |
171 | echo -e "${GREEN}2.${NC} Test the setup by creating a release:"
172 | echo " git tag v0.1.1"
173 | echo " git push origin v0.1.1"
174 | echo ""
175 |
176 | echo -e "${GREEN}3.${NC} Monitor the GitHub Actions workflow"
177 | echo ""
178 |
179 | echo -e "${GREEN}4.${NC} Test update checking in the compiled app"
180 | echo ""
181 |
182 | echo "🎉 Verification complete!"
183 | echo "For detailed setup instructions, see: UPDATES.md"
184 |
--------------------------------------------------------------------------------