Refactor to desktop application with Electron
Major Changes: - Convert web game to cross-platform desktop app using Electron - Add complete desktop file management system - Implement native file dialogs and unlimited storage - Create secure IPC bridge for file operations New Files Added: - main.js: Electron main process with native OS integration - preload.js: Secure IPC bridge between main and renderer - desktop-file-manager.js: Full file system access and operations - package.json: Electron dependencies and build configuration - setup.bat/setup.sh: Installation scripts for all platforms - README-DESKTOP.md: Comprehensive desktop application guide Desktop Features: - Native file import dialogs (no browser limitations) - Unlimited disk space for image storage - Direct folder access and file management - Cross-platform builds (Windows/Mac/Linux) - Full offline functionality - Native performance without web constraints Benefits: - Solves browser security sandbox limitations - Unlimited image storage using file system - Professional native application experience - Easy installation and distribution - True cross-platform compatibility
This commit is contained in:
parent
1879a8970f
commit
8e7cf0d4bf
|
|
@ -0,0 +1,228 @@
|
|||
# Task Challenge Game - Desktop Application
|
||||
|
||||
A modern, cross-platform desktop game built with Electron that challenges you to complete tasks or face consequences, with full image management capabilities.
|
||||
|
||||
## 🎮 Features
|
||||
|
||||
### Game Mechanics
|
||||
- **Task Challenges**: Complete randomly selected tasks
|
||||
- **Consequence System**: Skip tasks to face consequence challenges
|
||||
- **Progressive Difficulty**: Smart difficulty adjustment based on performance
|
||||
- **Scoring & Stats**: Track your progress with detailed statistics
|
||||
- **Custom Tasks**: Add your own personalized tasks
|
||||
|
||||
### Desktop Features
|
||||
- **🖥️ Native File Management**: Full file system access without browser limitations
|
||||
- **📁 Drag & Drop Support**: Easy image importing with native file dialogs
|
||||
- **💾 Unlimited Storage**: No browser storage quotas - use your full disk space
|
||||
- **🎵 Background Music**: Optional music playback with volume controls
|
||||
- **🌓 Theme Support**: Light and dark themes with system integration
|
||||
- **⚡ Fast Performance**: Native desktop performance without web constraints
|
||||
|
||||
### Image Management
|
||||
- **📋 Task Images**: Import and manage images for regular tasks
|
||||
- **⚠️ Consequence Images**: Import and manage images for consequence tasks
|
||||
- **🔍 Auto-Discovery**: Automatically scan directories for new images
|
||||
- **📂 Direct Folder Access**: Open image folders directly from the app
|
||||
- **🗑️ File Operations**: Delete, organize, and manage images with native tools
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Prerequisites
|
||||
- **Node.js** (v16 or higher) - [Download here](https://nodejs.org/)
|
||||
- **npm** (comes with Node.js)
|
||||
|
||||
### Installation
|
||||
|
||||
#### Windows
|
||||
```bash
|
||||
# Run the setup script
|
||||
setup.bat
|
||||
|
||||
# Or manually:
|
||||
npm install
|
||||
```
|
||||
|
||||
#### macOS/Linux
|
||||
```bash
|
||||
# Run the setup script
|
||||
chmod +x setup.sh
|
||||
./setup.sh
|
||||
|
||||
# Or manually:
|
||||
npm install
|
||||
```
|
||||
|
||||
### Running the Application
|
||||
|
||||
```bash
|
||||
# Start the desktop application
|
||||
npm start
|
||||
|
||||
# Development mode (with DevTools)
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Building Executables
|
||||
|
||||
```bash
|
||||
# Build for all platforms
|
||||
npm run build
|
||||
|
||||
# Build for specific platforms
|
||||
npm run build-win # Windows
|
||||
npm run build-mac # macOS
|
||||
npm run build-linux # Linux
|
||||
```
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
task-challenge-game/
|
||||
├── main.js # Electron main process
|
||||
├── preload.js # Secure IPC bridge
|
||||
├── desktop-file-manager.js # Desktop file operations
|
||||
├── index.html # Main UI
|
||||
├── game.js # Core game logic
|
||||
├── styles.css # Styling and themes
|
||||
├── gameData.js # Game data and tasks
|
||||
├── images/ # Image assets
|
||||
│ ├── tasks/ # Task images
|
||||
│ └── consequences/ # Consequence images
|
||||
├── audio/ # Background music
|
||||
├── package.json # Dependencies and build config
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## 🎯 How to Play
|
||||
|
||||
1. **Start the Game**: Click "Start Game" from the main menu
|
||||
2. **Choose Task Time**: Select your preferred task duration
|
||||
3. **Complete Tasks**: Follow the displayed instructions
|
||||
4. **Make Choices**: Complete the task or skip to face consequences
|
||||
5. **Track Progress**: Monitor your score and statistics
|
||||
|
||||
### Game Mechanics
|
||||
|
||||
- **✅ Complete Task**: Earn points and maintain your streak
|
||||
- **⏭️ Skip Task**: Face a consequence task with penalty
|
||||
- **🎯 Consequence Tasks**: More challenging tasks when you skip
|
||||
- **📈 Difficulty Scaling**: Tasks adapt based on your performance
|
||||
- **🏆 Scoring**: Points for completed tasks, penalties for skipping
|
||||
|
||||
## 🖼️ Image Management
|
||||
|
||||
### Adding Images
|
||||
|
||||
#### Desktop Method (Recommended)
|
||||
1. Go to "Manage Images" from the main menu
|
||||
2. Click "Import Task Images" or "Import Consequence Images"
|
||||
3. Select multiple images using the native file dialog
|
||||
4. Images are automatically copied to the app directory
|
||||
|
||||
#### Manual Method
|
||||
1. Place images in the appropriate folders:
|
||||
- `images/tasks/` for task images
|
||||
- `images/consequences/` for consequence images
|
||||
2. Click "Scan Image Directories" to detect new files
|
||||
|
||||
### Supported Formats
|
||||
- JPG/JPEG
|
||||
- PNG
|
||||
- GIF (including animated)
|
||||
- WebP
|
||||
- BMP
|
||||
|
||||
### Image Organization
|
||||
- **Task Images**: Fun, motivational, or neutral images
|
||||
- **Consequence Images**: More serious or challenging imagery
|
||||
- **Auto-Classification**: Images are automatically sorted by filename patterns
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
### Settings Storage
|
||||
Settings are stored in:
|
||||
- **Windows**: `%APPDATA%/task-challenge-game/`
|
||||
- **macOS**: `~/Library/Application Support/task-challenge-game/`
|
||||
- **Linux**: `~/.config/task-challenge-game/`
|
||||
|
||||
### Custom Tasks
|
||||
Add your own tasks by editing the in-game task manager or modifying `gameData.js`.
|
||||
|
||||
## 🔧 Development
|
||||
|
||||
### Development Setup
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Run in development mode
|
||||
npm run dev
|
||||
|
||||
# The app will reload automatically when files change
|
||||
```
|
||||
|
||||
### Project Architecture
|
||||
- **Electron Main Process**: `main.js` handles app lifecycle and native features
|
||||
- **Renderer Process**: `game.js` contains the game logic and UI
|
||||
- **IPC Communication**: `preload.js` provides secure bridge between processes
|
||||
- **File Management**: `desktop-file-manager.js` handles all file operations
|
||||
|
||||
### Adding Features
|
||||
1. Add UI elements to `index.html`
|
||||
2. Implement logic in `game.js` or create new modules
|
||||
3. Add IPC handlers in `main.js` if native features are needed
|
||||
4. Update `preload.js` to expose new APIs to the renderer
|
||||
|
||||
## 🌐 Web vs Desktop
|
||||
|
||||
| Feature | Web Version | Desktop Version |
|
||||
|---------|------------|-----------------|
|
||||
| Image Import | Limited browser upload | Native file dialogs |
|
||||
| Storage | Browser localStorage (limited) | Unlimited disk space |
|
||||
| File Management | Basic browser operations | Full file system access |
|
||||
| Performance | Browser constraints | Native performance |
|
||||
| Installation | None required | One-time setup |
|
||||
| Offline Usage | Limited | Full offline support |
|
||||
|
||||
## 🛠️ Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Images not showing up**
|
||||
- Check that images are in the correct folders
|
||||
- Run "Scan Image Directories" to refresh
|
||||
- Verify image formats are supported
|
||||
|
||||
**App won't start**
|
||||
- Ensure Node.js is installed and up to date
|
||||
- Run `npm install` to install dependencies
|
||||
- Check console for error messages
|
||||
|
||||
**Build failures**
|
||||
- Make sure all dependencies are installed
|
||||
- Check that you have write permissions in the project directory
|
||||
- Try clearing `node_modules` and reinstalling
|
||||
|
||||
### Getting Help
|
||||
|
||||
1. Check the console (F12) for error messages
|
||||
2. Verify all files are in the correct locations
|
||||
3. Ensure you have the latest version of Node.js
|
||||
4. Try rebuilding: `rm -rf node_modules && npm install`
|
||||
|
||||
## 📜 License
|
||||
|
||||
MIT License - feel free to modify and distribute!
|
||||
|
||||
## 🎵 Credits
|
||||
|
||||
- **Electron** - Cross-platform desktop framework
|
||||
- **Node.js** - JavaScript runtime
|
||||
- Built with modern web technologies (HTML5, CSS3, ES6+)
|
||||
|
||||
---
|
||||
|
||||
**Ready to challenge yourself?** 🚀
|
||||
|
||||
Run `npm start` and begin your task challenge adventure!
|
||||
|
|
@ -0,0 +1,246 @@
|
|||
// Desktop File Manager for Task Challenge Game
|
||||
class DesktopFileManager {
|
||||
constructor(dataManager) {
|
||||
this.dataManager = dataManager;
|
||||
this.appPath = null;
|
||||
this.imageDirectories = {
|
||||
tasks: null,
|
||||
consequences: null
|
||||
};
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
// Check if we're running in Electron
|
||||
this.isElectron = window.electronAPI !== undefined;
|
||||
|
||||
if (this.isElectron) {
|
||||
this.appPath = await window.electronAPI.getAppPath();
|
||||
this.imageDirectories.tasks = await window.electronAPI.pathJoin(this.appPath, 'images', 'tasks');
|
||||
this.imageDirectories.consequences = await window.electronAPI.pathJoin(this.appPath, 'images', 'consequences');
|
||||
|
||||
// Ensure directories exist
|
||||
await window.electronAPI.createDirectory(this.imageDirectories.tasks);
|
||||
await window.electronAPI.createDirectory(this.imageDirectories.consequences);
|
||||
|
||||
console.log('Desktop file manager initialized');
|
||||
console.log('App path:', this.appPath);
|
||||
console.log('Image directories:', this.imageDirectories);
|
||||
} else {
|
||||
console.log('Running in browser mode - file manager disabled');
|
||||
}
|
||||
}
|
||||
|
||||
async selectAndImportImages(category = 'task') {
|
||||
if (!this.isElectron) {
|
||||
this.showNotification('File import only available in desktop version', 'warning');
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
// Open file dialog
|
||||
const filePaths = await window.electronAPI.selectImages();
|
||||
|
||||
if (filePaths.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const importedImages = [];
|
||||
const targetDir = this.imageDirectories[category];
|
||||
|
||||
for (const filePath of filePaths) {
|
||||
const fileName = filePath.split(/[\\/]/).pop();
|
||||
const targetPath = await window.electronAPI.pathJoin(targetDir, fileName);
|
||||
|
||||
// Copy file to app directory
|
||||
const success = await window.electronAPI.copyImage(filePath, targetPath);
|
||||
|
||||
if (success) {
|
||||
importedImages.push({
|
||||
name: fileName,
|
||||
path: targetPath,
|
||||
category: category
|
||||
});
|
||||
console.log(`Imported: ${fileName} to ${category}`);
|
||||
} else {
|
||||
console.error(`Failed to import: ${fileName}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (importedImages.length > 0) {
|
||||
// Update the game's image storage
|
||||
await this.updateImageStorage(importedImages);
|
||||
this.showNotification(`Imported ${importedImages.length} image(s) to ${category}!`, 'success');
|
||||
}
|
||||
|
||||
return importedImages;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error importing images:', error);
|
||||
this.showNotification('Failed to import images', 'error');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async scanDirectoryForImages(category = 'task') {
|
||||
if (!this.isElectron) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const targetDir = this.imageDirectories[category];
|
||||
const images = await window.electronAPI.readDirectory(targetDir);
|
||||
|
||||
console.log(`Found ${images.length} images in ${category} directory`);
|
||||
return images.map(img => ({
|
||||
...img,
|
||||
category: category
|
||||
}));
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error scanning ${category} directory:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async scanAllDirectories() {
|
||||
if (!this.isElectron) {
|
||||
this.showNotification('Directory scanning only available in desktop version', 'warning');
|
||||
return { task: [], consequence: [] };
|
||||
}
|
||||
|
||||
const taskImages = await this.scanDirectoryForImages('tasks');
|
||||
const consequenceImages = await this.scanDirectoryForImages('consequences');
|
||||
|
||||
const results = {
|
||||
task: taskImages,
|
||||
consequence: consequenceImages
|
||||
};
|
||||
|
||||
// Update game storage
|
||||
if (taskImages.length > 0 || consequenceImages.length > 0) {
|
||||
await this.updateImageStorage([...taskImages, ...consequenceImages]);
|
||||
}
|
||||
|
||||
const totalFound = taskImages.length + consequenceImages.length;
|
||||
this.showNotification(`Found ${totalFound} images (${taskImages.length} tasks, ${consequenceImages.length} consequences)`, 'success');
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async updateImageStorage(images) {
|
||||
// Get existing images
|
||||
let customImages = this.dataManager.get('customImages') || { task: [], consequence: [] };
|
||||
|
||||
// Convert old format if necessary
|
||||
if (Array.isArray(customImages)) {
|
||||
customImages = { task: customImages, consequence: [] };
|
||||
}
|
||||
|
||||
// Add new images (avoid duplicates)
|
||||
for (const image of images) {
|
||||
const category = image.category === 'tasks' ? 'task' :
|
||||
image.category === 'consequences' ? 'consequence' :
|
||||
image.category;
|
||||
|
||||
if (!customImages[category]) {
|
||||
customImages[category] = [];
|
||||
}
|
||||
|
||||
// Check for duplicates by path
|
||||
const exists = customImages[category].some(existing => {
|
||||
if (typeof existing === 'string') {
|
||||
return existing === image.path;
|
||||
} else if (typeof existing === 'object') {
|
||||
return existing.path === image.path;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!exists) {
|
||||
customImages[category].push(image.path);
|
||||
}
|
||||
}
|
||||
|
||||
// Save updated images
|
||||
this.dataManager.set('customImages', customImages);
|
||||
|
||||
return customImages;
|
||||
}
|
||||
|
||||
async deleteImage(imagePath, category) {
|
||||
if (!this.isElectron) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const success = await window.electronAPI.deleteFile(imagePath);
|
||||
|
||||
if (success) {
|
||||
// Remove from storage
|
||||
let customImages = this.dataManager.get('customImages') || { task: [], consequence: [] };
|
||||
if (Array.isArray(customImages)) {
|
||||
customImages = { task: customImages, consequence: [] };
|
||||
}
|
||||
|
||||
if (customImages[category]) {
|
||||
customImages[category] = customImages[category].filter(img => {
|
||||
if (typeof img === 'string') {
|
||||
return img !== imagePath;
|
||||
} else if (typeof img === 'object') {
|
||||
return img.path !== imagePath;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
this.dataManager.set('customImages', customImages);
|
||||
this.showNotification('Image deleted successfully', 'success');
|
||||
return true;
|
||||
} else {
|
||||
this.showNotification('Failed to delete image', 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error deleting image:', error);
|
||||
this.showNotification('Error deleting image', 'error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async openImageDirectory(category = 'task') {
|
||||
if (!this.isElectron) {
|
||||
this.showNotification('This feature is only available in desktop version', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: We would need to add shell integration to open the folder
|
||||
// For now, just show the path
|
||||
const dir = this.imageDirectories[category === 'task' ? 'tasks' : 'consequences'];
|
||||
this.showNotification(`Images stored in: ${dir}`, 'info');
|
||||
console.log(`${category} images directory:`, dir);
|
||||
}
|
||||
|
||||
showNotification(message, type = 'info') {
|
||||
// Use the game's existing notification system
|
||||
if (window.game && window.game.showNotification) {
|
||||
window.game.showNotification(message, type);
|
||||
} else {
|
||||
console.log(`[${type.toUpperCase()}] ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
getImagePath(imageName, category = 'task') {
|
||||
if (!this.isElectron) {
|
||||
return `images/${category}s/${imageName}`;
|
||||
}
|
||||
|
||||
const dir = this.imageDirectories[category === 'task' ? 'tasks' : 'consequences'];
|
||||
return `${dir}/${imageName}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Global file manager instance
|
||||
let desktopFileManager = null;
|
||||
32
game.js
32
game.js
|
|
@ -4,6 +4,9 @@ class TaskChallengeGame {
|
|||
// Initialize data management system first
|
||||
this.dataManager = new DataManager();
|
||||
|
||||
// Initialize desktop features early
|
||||
this.initDesktopFeatures();
|
||||
|
||||
this.gameState = {
|
||||
isRunning: false,
|
||||
isPaused: false,
|
||||
|
|
@ -35,6 +38,35 @@ class TaskChallengeGame {
|
|||
this.checkAutoResume();
|
||||
}
|
||||
|
||||
async initDesktopFeatures() {
|
||||
// Initialize desktop file manager
|
||||
if (typeof DesktopFileManager !== 'undefined') {
|
||||
this.fileManager = new DesktopFileManager(this.dataManager);
|
||||
window.desktopFileManager = this.fileManager;
|
||||
}
|
||||
|
||||
// Check if we're in Electron and update UI accordingly
|
||||
setTimeout(() => {
|
||||
const isElectron = window.electronAPI !== undefined;
|
||||
|
||||
if (isElectron) {
|
||||
document.body.classList.add('desktop-mode');
|
||||
// Show desktop-specific features
|
||||
document.querySelectorAll('.desktop-only').forEach(el => el.style.display = '');
|
||||
document.querySelectorAll('.desktop-feature').forEach(el => el.style.display = '');
|
||||
document.querySelectorAll('.web-feature').forEach(el => el.style.display = 'none');
|
||||
console.log('🖥️ Desktop mode activated');
|
||||
} else {
|
||||
document.body.classList.add('web-mode');
|
||||
// Hide desktop-only features
|
||||
document.querySelectorAll('.desktop-only').forEach(el => el.style.display = 'none');
|
||||
document.querySelectorAll('.desktop-feature').forEach(el => el.style.display = 'none');
|
||||
document.querySelectorAll('.web-feature').forEach(el => el.style.display = '');
|
||||
console.log('🌐 Web mode activated');
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
initializeCustomTasks() {
|
||||
// Load custom tasks from localStorage or use defaults
|
||||
const savedMainTasks = localStorage.getItem('customMainTasks');
|
||||
|
|
|
|||
14
index.html
14
index.html
|
|
@ -138,17 +138,20 @@
|
|||
|
||||
<!-- Upload Section -->
|
||||
<div class="upload-section">
|
||||
<h3>Add New Images</h3>
|
||||
<h3>📁 Import Images</h3>
|
||||
<div class="upload-controls">
|
||||
<button id="upload-images-btn" class="btn btn-primary">📁 Upload Images</button>
|
||||
<button id="import-task-images-btn" class="btn btn-primary"><EFBFBD> Import Task Images</button>
|
||||
<button id="import-consequence-images-btn" class="btn btn-warning">⚠️ Import Consequence Images</button>
|
||||
<input type="file" id="image-upload-input" accept="image/*" multiple style="display: none;">
|
||||
<span class="upload-info">Supported formats: JPG, PNG, GIF, WebP</span>
|
||||
<span class="upload-info desktop-feature">Desktop: Native file dialogs with unlimited storage</span>
|
||||
<span class="upload-info web-feature" style="display: none;">Web: Limited browser upload</span>
|
||||
</div>
|
||||
<div class="directory-controls">
|
||||
<button id="scan-directories-btn" class="btn btn-secondary">🔍 Scan Directories for New Images</button>
|
||||
<button id="scan-directories-btn" class="btn btn-secondary">🔍 Scan Image Directories</button>
|
||||
<button id="open-image-folders-btn" class="btn btn-outline desktop-only">📂 Open Image Folders</button>
|
||||
<button id="clear-image-cache-btn" class="btn btn-outline">🗑️ Clear Image Cache</button>
|
||||
<button id="storage-info-btn" class="btn btn-outline">📊 Storage Info</button>
|
||||
<span class="scan-info">Scans images/tasks/ and images/consequences/ folders</span>
|
||||
<span class="scan-info">Automatically finds images in your game directories</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -256,6 +259,7 @@
|
|||
</div>
|
||||
|
||||
<script src="gameData.js"></script>
|
||||
<script src="desktop-file-manager.js"></script>
|
||||
<script src="game.js"></script>
|
||||
<!-- Statistics Modal -->
|
||||
<div id="stats-modal" class="modal" style="display: none;">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
const { app, BrowserWindow, dialog, ipcMain } = require('electron');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
|
||||
let mainWindow;
|
||||
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
minWidth: 800,
|
||||
minHeight: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
enableRemoteModule: false,
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
},
|
||||
icon: path.join(__dirname, 'assets', 'icon.png'),
|
||||
show: false
|
||||
});
|
||||
|
||||
mainWindow.loadFile('index.html');
|
||||
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
mainWindow.show();
|
||||
});
|
||||
|
||||
// Open DevTools in development
|
||||
if (process.argv.includes('--dev')) {
|
||||
mainWindow.webContents.openDevTools();
|
||||
}
|
||||
}
|
||||
|
||||
app.whenReady().then(createWindow);
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
// File system operations for the renderer process
|
||||
ipcMain.handle('select-images', async () => {
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openFile', 'multiSelections'],
|
||||
filters: [
|
||||
{ name: 'Images', extensions: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'] }
|
||||
]
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
return result.filePaths;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
ipcMain.handle('select-directory', async () => {
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openDirectory']
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
return result.filePaths[0];
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
ipcMain.handle('read-directory', async (event, dirPath) => {
|
||||
try {
|
||||
const files = await fs.readdir(dirPath);
|
||||
const imageFiles = files.filter(file => {
|
||||
const ext = path.extname(file).toLowerCase();
|
||||
return ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp'].includes(ext);
|
||||
});
|
||||
|
||||
return imageFiles.map(file => ({
|
||||
name: file,
|
||||
path: path.join(dirPath, file)
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error reading directory:', error);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('copy-image', async (event, sourcePath, destPath) => {
|
||||
try {
|
||||
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
||||
await fs.copyFile(sourcePath, destPath);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error copying image:', error);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-app-path', () => {
|
||||
return app.getAppPath();
|
||||
});
|
||||
|
||||
ipcMain.handle('path-join', (event, ...paths) => {
|
||||
return path.join(...paths);
|
||||
});
|
||||
|
||||
ipcMain.handle('file-exists', async (event, filePath) => {
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('create-directory', async (event, dirPath) => {
|
||||
try {
|
||||
await fs.mkdir(dirPath, { recursive: true });
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error creating directory:', error);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('delete-file', async (event, filePath) => {
|
||||
try {
|
||||
await fs.unlink(filePath);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error deleting file:', error);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"name": "task-challenge-game",
|
||||
"version": "1.0.0",
|
||||
"description": "A desktop task challenge game with image management",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"build": "electron-builder",
|
||||
"build-win": "electron-builder --win",
|
||||
"build-mac": "electron-builder --mac",
|
||||
"build-linux": "electron-builder --linux",
|
||||
"dev": "electron . --dev"
|
||||
},
|
||||
"author": "Task Challenge Game Developer",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"electron": "^27.0.0",
|
||||
"electron-builder": "^24.6.4"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.taskchallenge.game",
|
||||
"productName": "Task Challenge Game",
|
||||
"directories": {
|
||||
"output": "dist"
|
||||
},
|
||||
"files": [
|
||||
"**/*",
|
||||
"!node_modules/**/*",
|
||||
"!src/**/*",
|
||||
"!dist/**/*",
|
||||
"!.git/**/*"
|
||||
],
|
||||
"win": {
|
||||
"target": "nsis",
|
||||
"icon": "assets/icon.ico"
|
||||
},
|
||||
"mac": {
|
||||
"target": "dmg",
|
||||
"icon": "assets/icon.icns"
|
||||
},
|
||||
"linux": {
|
||||
"target": "AppImage",
|
||||
"icon": "assets/icon.png"
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "."
|
||||
},
|
||||
"keywords": [
|
||||
"game",
|
||||
"task",
|
||||
"challenge",
|
||||
"desktop",
|
||||
"electron"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
|
||||
// Expose protected methods that allow the renderer process to use
|
||||
// the ipcRenderer without exposing the entire object
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
// Image file operations
|
||||
selectImages: () => ipcRenderer.invoke('select-images'),
|
||||
selectDirectory: () => ipcRenderer.invoke('select-directory'),
|
||||
readDirectory: (dirPath) => ipcRenderer.invoke('read-directory', dirPath),
|
||||
copyImage: (sourcePath, destPath) => ipcRenderer.invoke('copy-image', sourcePath, destPath),
|
||||
|
||||
// File system utilities
|
||||
getAppPath: () => ipcRenderer.invoke('get-app-path'),
|
||||
pathJoin: (...paths) => ipcRenderer.invoke('path-join', ...paths),
|
||||
fileExists: (filePath) => ipcRenderer.invoke('file-exists', filePath),
|
||||
createDirectory: (dirPath) => ipcRenderer.invoke('create-directory', dirPath),
|
||||
deleteFile: (filePath) => ipcRenderer.invoke('delete-file', filePath),
|
||||
|
||||
// Platform info
|
||||
platform: process.platform,
|
||||
|
||||
// Version info
|
||||
versions: {
|
||||
node: process.versions.node,
|
||||
electron: process.versions.electron
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
@echo off
|
||||
echo 🎮 Task Challenge Game - Desktop Setup
|
||||
echo ======================================
|
||||
|
||||
:: Check if Node.js is installed
|
||||
node --version >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo ❌ Node.js is not installed. Please install Node.js first:
|
||||
echo https://nodejs.org/en/download/
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: Check if npm is installed
|
||||
npm --version >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo ❌ npm is not installed. Please install npm first.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo ✅ Node.js and npm are installed
|
||||
|
||||
:: Install dependencies
|
||||
echo 📦 Installing dependencies...
|
||||
npm install
|
||||
|
||||
if errorlevel 0 (
|
||||
echo ✅ Dependencies installed successfully!
|
||||
echo.
|
||||
echo 🚀 Ready to run!
|
||||
echo.
|
||||
echo Available commands:
|
||||
echo npm start - Run the desktop application
|
||||
echo npm run dev - Run with developer tools
|
||||
echo npm run build - Build executable
|
||||
echo.
|
||||
pause
|
||||
) else (
|
||||
echo ❌ Failed to install dependencies
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#!/bin/bash
|
||||
# Setup script for Task Challenge Game Desktop Application
|
||||
|
||||
echo "🎮 Task Challenge Game - Desktop Setup"
|
||||
echo "======================================"
|
||||
|
||||
# Check if Node.js is installed
|
||||
if ! command -v node &> /dev/null; then
|
||||
echo "❌ Node.js is not installed. Please install Node.js first:"
|
||||
echo " https://nodejs.org/en/download/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if npm is installed
|
||||
if ! command -v npm &> /dev/null; then
|
||||
echo "❌ npm is not installed. Please install npm first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Node.js and npm are installed"
|
||||
|
||||
# Install dependencies
|
||||
echo "📦 Installing dependencies..."
|
||||
npm install
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Dependencies installed successfully!"
|
||||
echo ""
|
||||
echo "🚀 Ready to run!"
|
||||
echo ""
|
||||
echo "Available commands:"
|
||||
echo " npm start - Run the desktop application"
|
||||
echo " npm run dev - Run with developer tools"
|
||||
echo " npm run build - Build executable"
|
||||
echo ""
|
||||
else
|
||||
echo "❌ Failed to install dependencies"
|
||||
exit 1
|
||||
fi
|
||||
Loading…
Reference in New Issue