mirror of
https://github.com/dualshock-tools/dualshock-tools.github.io.git
synced 2026-03-01 11:19:54 +03:00
Build with Gulp
This commit is contained in:
committed by
dualshock-tools
parent
3493c4de74
commit
7d4ddfdfad
58
.gitignore
vendored
Normal file
58
.gitignore
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
.tmp/
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage/
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Dependency directories
|
||||
jspm_packages/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
136
DEVELOPMENT.md
Normal file
136
DEVELOPMENT.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# Development Guide
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Start development with auto-reload
|
||||
npm run dev:full
|
||||
```
|
||||
|
||||
Open `https://localhost:8443` in your browser (accept the SSL certificate warning).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
| Script | Description |
|
||||
| --------------------- | --------------------------------------------------------- |
|
||||
| `npm run build` | Build for development (with source maps) |
|
||||
| `npm run build:prod` | Build for production (minified, optimized) |
|
||||
| `npm run clean` | Clean the dist directory |
|
||||
| `npm run serve:https` | Serve built app over HTTPS (required for WebHID) |
|
||||
| `npm run serve` | Serve built app over HTTP (WebHID won't work) |
|
||||
| `npm run start` | Build and serve over HTTPS |
|
||||
| `npm run dev:full` | **Recommended**: Build, watch, and serve with auto-reload |
|
||||
| `npm run watch` | Watch files and rebuild on changes |
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### For Active Development
|
||||
|
||||
```bash
|
||||
npm run dev:full
|
||||
```
|
||||
|
||||
This starts the complete development environment:
|
||||
|
||||
- Builds the application
|
||||
- Watches for file changes
|
||||
- Serves over HTTPS at `https://localhost:8443`
|
||||
- Automatically rebuilds when you save files
|
||||
|
||||
### For Testing Built Version
|
||||
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
|
||||
This builds once and serves the result.
|
||||
|
||||
## Important Notes
|
||||
|
||||
### HTTPS Requirement
|
||||
|
||||
The WebHID API requires HTTPS. The development server uses self-signed certificates located at:
|
||||
|
||||
- `server.crt` - SSL certificate
|
||||
- `server.key` - SSL private key
|
||||
|
||||
### Browser Compatibility
|
||||
|
||||
- **Chrome/Edge**: Full WebHID support ✅
|
||||
- **Firefox**: No WebHID support ❌
|
||||
- **Safari**: No WebHID support ❌
|
||||
|
||||
### SSL Certificate Warning
|
||||
|
||||
When first accessing `https://localhost:8443`, your browser will show a security warning because we're using a self-signed certificate. This is normal for development - click "Advanced" and "Proceed to localhost" to continue.
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
├── js/ # Source JavaScript files
|
||||
│ ├── core.js # Main application entry point
|
||||
│ ├── controllers/ # Controller-specific classes
|
||||
│ └── modals/ # Modal dialog handlers
|
||||
├── css/ # Source CSS files
|
||||
├── templates/ # HTML template files
|
||||
├── lang/ # Translation JSON files
|
||||
├── assets/ # SVG assets
|
||||
├── dist/ # Built application (auto-generated)
|
||||
└── dev-server.js # Custom development server
|
||||
```
|
||||
|
||||
## Build Process
|
||||
|
||||
The build system uses Gulp with the following steps:
|
||||
|
||||
1. **JavaScript**: Bundled with Rollup, supports ES modules
|
||||
2. **CSS**: Concatenated and optionally minified
|
||||
3. **HTML**: Processed and optionally minified
|
||||
4. **Assets**: Copied to dist, SVGs can be inlined in production
|
||||
5. **Languages**: JSON files copied and optionally minified
|
||||
|
||||
### Development vs Production
|
||||
|
||||
| Feature | Development | Production |
|
||||
| -------------- | ----------- | ---------- |
|
||||
| Source maps | ✅ | ❌ |
|
||||
| Minification | ❌ | ✅ |
|
||||
| Asset inlining | ❌ | ✅ |
|
||||
| File hashing | ❌ | ✅ |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Port Already in Use
|
||||
|
||||
If port 8443 is busy:
|
||||
|
||||
```bash
|
||||
PORT=8444 npm run serve:https
|
||||
```
|
||||
|
||||
### Build Errors
|
||||
|
||||
Clean and rebuild:
|
||||
|
||||
```bash
|
||||
npm run clean
|
||||
npm run build
|
||||
```
|
||||
|
||||
### SSL Certificate Issues
|
||||
|
||||
The certificates are pre-generated. If you need new ones:
|
||||
|
||||
```bash
|
||||
openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes -subj "/CN=localhost"
|
||||
```
|
||||
|
||||
### WebHID Not Working
|
||||
|
||||
1. Make sure you're using HTTPS (`npm run serve:https`)
|
||||
2. Use Chrome or Edge browser
|
||||
3. Accept the SSL certificate warning
|
||||
4. Check browser console for errors
|
||||
94
README.md
94
README.md
@@ -1,3 +1,93 @@
|
||||
# dualshock-tools.github.io
|
||||
# DualShock Calibration GUI
|
||||
|
||||
The code behind the DualShock Calibration GUI
|
||||
A web-based calibration tool for PlayStation DualShock 4, DualSense, and DualSense Edge controllers using the WebHID API.
|
||||
|
||||
## Features
|
||||
|
||||
- Controller connection via WebHID API
|
||||
- Stick calibration and range calibration
|
||||
- Input testing and visualization
|
||||
- Battery status display
|
||||
- Multi-language support (20+ languages)
|
||||
- Progressive Web App capabilities
|
||||
|
||||
## Development
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js (v16 or higher)
|
||||
- npm or yarn
|
||||
- Modern browser with WebHID support (Chrome/Edge)
|
||||
|
||||
### Getting Started
|
||||
|
||||
1. **Install dependencies:**
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. **Build the application:**
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
3. **Start the development server:**
|
||||
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
|
||||
The app will be available at `https://localhost:8443`
|
||||
|
||||
### Development Scripts
|
||||
|
||||
- `npm run build` - Build the application for development
|
||||
- `npm run build:prod` - Build the application for production
|
||||
- `npm run clean` - Clean the dist directory
|
||||
- `npm run serve:https` - Serve the built app over HTTPS (required for WebHID)
|
||||
- `npm run serve` - Serve the built app over HTTP (WebHID won't work)
|
||||
- `npm run start` - Build and serve the app
|
||||
- `npm run dev:full` - Build, watch for changes, and serve with auto-reload
|
||||
- `npm run watch` - Watch for file changes and rebuild
|
||||
|
||||
### Development Workflow
|
||||
|
||||
For active development with auto-rebuild:
|
||||
|
||||
```bash
|
||||
npm run dev:full
|
||||
```
|
||||
|
||||
This will:
|
||||
|
||||
1. Build the application
|
||||
2. Start watching for file changes
|
||||
3. Serve the app over HTTPS at `https://localhost:8443`
|
||||
4. Automatically rebuild when files change
|
||||
|
||||
### Important Notes
|
||||
|
||||
- **HTTPS Required**: The WebHID API requires HTTPS. The development server uses self-signed certificates.
|
||||
- **Browser Security**: You may need to accept the self-signed certificate warning in your browser.
|
||||
- **Controller Support**: Only works in browsers with WebHID support (Chrome, Edge, Opera).
|
||||
|
||||
### Project Structure
|
||||
|
||||
- `js/` - Source JavaScript files
|
||||
- `css/` - Source CSS files
|
||||
- `templates/` - HTML template files
|
||||
- `lang/` - Translation files
|
||||
- `assets/` - SVG assets
|
||||
- `dist/` - Built application (generated)
|
||||
|
||||
### Build System
|
||||
|
||||
The project uses Gulp for building:
|
||||
|
||||
- JavaScript bundling with Rollup
|
||||
- CSS concatenation and minification
|
||||
- HTML processing and minification
|
||||
- Asset optimization
|
||||
- Development vs production builds
|
||||
|
||||
183
dev-server.js
Normal file
183
dev-server.js
Normal file
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import https from 'https';
|
||||
import http from 'http';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
// Configuration
|
||||
const config = {
|
||||
port: process.env.PORT || 8443,
|
||||
httpPort: process.env.HTTP_PORT || 8080,
|
||||
host: process.env.HOST || 'localhost',
|
||||
distDir: path.join(__dirname, 'dist'),
|
||||
certFile: path.join(__dirname, 'server.crt'),
|
||||
keyFile: path.join(__dirname, 'server.key'),
|
||||
useHttps: process.env.HTTPS === 'true'
|
||||
};
|
||||
|
||||
// MIME types
|
||||
const mimeTypes = {
|
||||
'.html': 'text/html',
|
||||
'.js': 'text/javascript',
|
||||
'.css': 'text/css',
|
||||
'.json': 'application/json',
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.gif': 'image/gif',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.ico': 'image/x-icon',
|
||||
'.webmanifest': 'application/manifest+json'
|
||||
};
|
||||
|
||||
function getMimeType(filePath) {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
return mimeTypes[ext] || 'application/octet-stream';
|
||||
}
|
||||
|
||||
function requestHandler(req, res) {
|
||||
// Parse URL and remove query parameters
|
||||
let urlPath = new URL(req.url, `http://${req.headers.host}`).pathname;
|
||||
|
||||
// Default to index.html for root requests
|
||||
if (urlPath === '/') {
|
||||
urlPath = '/index.html';
|
||||
}
|
||||
|
||||
const filePath = path.join(config.distDir, urlPath);
|
||||
const mimeType = getMimeType(filePath);
|
||||
|
||||
// Security check - ensure file is within dist directory
|
||||
if (!filePath.startsWith(config.distDir)) {
|
||||
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
||||
res.end('Forbidden');
|
||||
return;
|
||||
}
|
||||
|
||||
// Set CORS headers for development
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
||||
|
||||
// Disable caching for development
|
||||
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||
res.setHeader('Pragma', 'no-cache');
|
||||
res.setHeader('Expires', '0');
|
||||
|
||||
// Handle OPTIONS requests
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
fs.readFile(filePath, (err, data) => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
// Try to serve index.html for SPA routing
|
||||
const indexPath = path.join(config.distDir, 'index.html');
|
||||
fs.readFile(indexPath, (indexErr, indexData) => {
|
||||
if (indexErr) {
|
||||
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
||||
res.end('Not Found');
|
||||
} else {
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
res.end(indexData);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
||||
res.end('Internal Server Error');
|
||||
}
|
||||
} else {
|
||||
res.writeHead(200, { 'Content-Type': mimeType });
|
||||
res.end(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function startServer() {
|
||||
// Check if dist directory exists
|
||||
if (!fs.existsSync(config.distDir)) {
|
||||
console.error(`❌ Dist directory not found: ${config.distDir}`);
|
||||
console.log('💡 Run "npm run build" first to build the application');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (config.useHttps) {
|
||||
// Check if SSL certificates exist
|
||||
if (!fs.existsSync(config.certFile) || !fs.existsSync(config.keyFile)) {
|
||||
console.error('❌ SSL certificates not found');
|
||||
console.log('💡 SSL certificates are required for WebHID API');
|
||||
console.log(' Make sure server.crt and server.key exist in the project root');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Read SSL certificates
|
||||
const options = {
|
||||
key: fs.readFileSync(config.keyFile),
|
||||
cert: fs.readFileSync(config.certFile)
|
||||
};
|
||||
|
||||
// Create HTTPS server
|
||||
const server = https.createServer(options, requestHandler);
|
||||
|
||||
server.listen(config.port, config.host, () => {
|
||||
console.log('🚀 Development server started!');
|
||||
console.log(`📱 App running at: https://${config.host}:${config.port}`);
|
||||
console.log('🔒 HTTPS enabled (required for WebHID API)');
|
||||
console.log('💡 Press Ctrl+C to stop the server');
|
||||
console.log('');
|
||||
console.log('📝 Note: You may need to accept the self-signed certificate in your browser');
|
||||
});
|
||||
|
||||
server.on('error', (err) => {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
console.error(`❌ Port ${config.port} is already in use`);
|
||||
console.log('💡 Try using a different port: PORT=8444 npm run serve:https');
|
||||
} else {
|
||||
console.error('❌ Server error:', err.message);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
} else {
|
||||
// Create HTTP server (for testing only - WebHID won't work)
|
||||
const server = http.createServer(requestHandler);
|
||||
|
||||
server.listen(config.httpPort, config.host, () => {
|
||||
console.log('🚀 Development server started!');
|
||||
console.log(`📱 App running at: http://${config.host}:${config.httpPort}`);
|
||||
console.log('⚠️ HTTP mode - WebHID API will only work on localhost');
|
||||
console.log('💡 Use "npm run serve:https" to enable WebHID support to other clients on the local network');
|
||||
console.log('💡 Press Ctrl+C to stop the server');
|
||||
});
|
||||
|
||||
server.on('error', (err) => {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
console.error(`❌ Port ${config.httpPort} is already in use`);
|
||||
console.log('💡 Try using a different port: HTTP_PORT=8081 npm run serve');
|
||||
} else {
|
||||
console.error('❌ Server error:', err.message);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle graceful shutdown
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\n👋 Shutting down development server...');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('\n👋 Shutting down development server...');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// Start the server
|
||||
startServer();
|
||||
318
gulpfile.js
Normal file
318
gulpfile.js
Normal file
@@ -0,0 +1,318 @@
|
||||
import gulp from 'gulp';
|
||||
import cleanCSS from 'gulp-clean-css';
|
||||
import htmlmin from 'gulp-htmlmin';
|
||||
import concat from 'gulp-concat';
|
||||
import sourcemaps from 'gulp-sourcemaps';
|
||||
import gulpif from 'gulp-if';
|
||||
import rename from 'gulp-rename';
|
||||
import { deleteAsync } from 'del';
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
import replace from 'gulp-replace';
|
||||
import jsonMinify from 'gulp-json-minify';
|
||||
import crypto from 'crypto';
|
||||
import { rollup } from 'rollup';
|
||||
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||
import rollupTerser from '@rollup/plugin-terser';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { glob } from 'glob';
|
||||
|
||||
// Get command line arguments
|
||||
const argv = yargs(hideBin(process.argv)).argv;
|
||||
const isProduction = argv.production || process.env.NODE_ENV === 'production';
|
||||
|
||||
// Paths configuration
|
||||
const paths = {
|
||||
src: {
|
||||
js: {
|
||||
entry: 'js/core.js',
|
||||
all: 'js/**/*.js'
|
||||
},
|
||||
css: ['css/main.css', 'css/finetune.css'],
|
||||
html: {
|
||||
main: 'index.html',
|
||||
templates: 'templates/**/*.html'
|
||||
},
|
||||
lang: 'lang/**/*.json',
|
||||
assets: [
|
||||
'favicon.ico',
|
||||
'favicon.svg',
|
||||
'favicon-16x16.png',
|
||||
'favicon-32x32.png',
|
||||
'favicon-96x96.png',
|
||||
'apple-touch-icon.png',
|
||||
'web-app-manifest-192x192.png',
|
||||
'web-app-manifest-512x512.png',
|
||||
'site.webmanifest',
|
||||
'donate.png',
|
||||
'googlec4c2e36a49e62fa3.html',
|
||||
'fa.min.css'
|
||||
],
|
||||
svg: 'assets/**/*.svg'
|
||||
},
|
||||
dist: 'dist',
|
||||
temp: '.tmp'
|
||||
};
|
||||
|
||||
// Clean task
|
||||
function clean() {
|
||||
return deleteAsync([paths.dist, paths.temp]);
|
||||
}
|
||||
|
||||
// JavaScript bundling with Rollup
|
||||
async function scripts() {
|
||||
const inputOptions = {
|
||||
input: paths.src.js.entry,
|
||||
plugins: [
|
||||
nodeResolve(),
|
||||
...(isProduction ? [
|
||||
rollupTerser({
|
||||
compress: {
|
||||
drop_console: false,
|
||||
drop_debugger: true,
|
||||
pure_funcs: ['console.debug', 'console.trace'],
|
||||
passes: 2,
|
||||
unsafe: true,
|
||||
unsafe_comps: true,
|
||||
unsafe_math: true,
|
||||
unsafe_proto: true
|
||||
},
|
||||
mangle: {
|
||||
reserved: [
|
||||
'gboot', 'connect', 'disconnect', 'disconnectSync',
|
||||
'show_faq_modal', 'calibrate_stick_centers', 'calibrate_range',
|
||||
'ds5_finetune', 'auto_calibrate_stick_centers', 'flash_all_changes',
|
||||
'reboot_controller', 'welcome_accepted', 'show_info_tab',
|
||||
'nvslock', 'nvsunlock', 'refresh_nvstatus', 'show_edge_modal',
|
||||
'show_donate_modal'
|
||||
],
|
||||
properties: {
|
||||
regex: /^_/
|
||||
}
|
||||
},
|
||||
format: {
|
||||
comments: false
|
||||
}
|
||||
})
|
||||
] : [])
|
||||
]
|
||||
};
|
||||
|
||||
let filename = 'app.js';
|
||||
|
||||
if (isProduction) {
|
||||
const bundle = await rollup(inputOptions);
|
||||
const { output } = await bundle.generate({ format: 'es' });
|
||||
const code = output[0].code;
|
||||
const hash = crypto.createHash('md5').update(code).digest('hex').substring(0, 8);
|
||||
filename = `app-${hash}.js`;
|
||||
|
||||
await fs.writeFile(path.join(paths.dist, filename), code);
|
||||
await bundle.close();
|
||||
} else {
|
||||
const outputOptions = {
|
||||
file: path.join(paths.dist, filename),
|
||||
format: 'es',
|
||||
sourcemap: true
|
||||
};
|
||||
|
||||
const bundle = await rollup(inputOptions);
|
||||
await bundle.write(outputOptions);
|
||||
await bundle.close();
|
||||
}
|
||||
|
||||
// Store the filename for HTML processing
|
||||
global.jsFilename = filename;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// CSS processing
|
||||
function styles() {
|
||||
let stream = gulp.src(paths.src.css)
|
||||
.pipe(gulpif(!isProduction, sourcemaps.init()))
|
||||
.pipe(concat('app.css'));
|
||||
|
||||
if (isProduction) {
|
||||
stream = stream.pipe(cleanCSS({
|
||||
level: 2
|
||||
}));
|
||||
|
||||
// Add hash to filename in production
|
||||
stream = stream.pipe(rename(function(path) {
|
||||
const hash = crypto.createHash('md5').update(Date.now().toString()).digest('hex').substring(0, 8);
|
||||
path.basename = `app-${hash}`;
|
||||
global.cssFilename = `${path.basename}.css`;
|
||||
}));
|
||||
} else {
|
||||
stream = stream.pipe(sourcemaps.write('.'));
|
||||
global.cssFilename = 'app.css';
|
||||
}
|
||||
|
||||
return stream.pipe(gulp.dest(paths.dist));
|
||||
}
|
||||
|
||||
// Bundle templates and SVG assets into HTML for production
|
||||
async function bundleAssets() {
|
||||
if (!isProduction) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
try {
|
||||
// Read all template files
|
||||
const templateFiles = await glob('templates/**/*.html');
|
||||
const templates = {};
|
||||
|
||||
for (const templateFile of templateFiles) {
|
||||
const content = await fs.readFile(templateFile, 'utf8');
|
||||
const templateName = path.basename(templateFile, '.html');
|
||||
templates[templateName] = content;
|
||||
}
|
||||
|
||||
// Read SVG assets
|
||||
const svgFiles = await glob('assets/**/*.svg');
|
||||
const svgAssets = {};
|
||||
|
||||
for (const svgFile of svgFiles) {
|
||||
const content = await fs.readFile(svgFile, 'utf8');
|
||||
const assetName = path.relative('assets', svgFile);
|
||||
svgAssets[assetName] = content;
|
||||
}
|
||||
|
||||
// Create the bundled assets object
|
||||
const bundledAssets = {
|
||||
templates,
|
||||
svg: svgAssets
|
||||
};
|
||||
|
||||
// Store for use in HTML processing
|
||||
global.bundledAssets = bundledAssets;
|
||||
return Promise.resolve();
|
||||
} catch (error) {
|
||||
console.error('Error bundling assets:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// HTML processing
|
||||
async function html() {
|
||||
const jsFile = global.jsFilename || 'app.js';
|
||||
const cssFile = global.cssFilename || 'app.css';
|
||||
|
||||
let htmlContent = await fs.readFile(paths.src.html.main, 'utf8');
|
||||
|
||||
// Replace script and CSS references
|
||||
htmlContent = htmlContent.replace('<script type="module" src="js/core.js"></script>', `<script type="module" src="${jsFile}"></script>`);
|
||||
htmlContent = htmlContent.replace('<link rel="stylesheet" href="css/main.css">', '');
|
||||
htmlContent = htmlContent.replace('<link rel="stylesheet" href="css/finetune.css">', `<link rel="stylesheet" href="${cssFile}">`);
|
||||
|
||||
// In production, inject bundled assets
|
||||
if (isProduction && global.bundledAssets) {
|
||||
const bundledAssetsScript = `
|
||||
<script type="text/javascript">
|
||||
window.BUNDLED_ASSETS = ${JSON.stringify(global.bundledAssets)};
|
||||
</script>`;
|
||||
|
||||
// Insert the bundled assets script before the main app script
|
||||
htmlContent = htmlContent.replace(
|
||||
`<script type="module" src="${jsFile}"></script>`,
|
||||
`${bundledAssetsScript}\n<script type="module" src="${jsFile}"></script>`
|
||||
);
|
||||
}
|
||||
|
||||
if (isProduction) {
|
||||
// Use htmlmin to minify the content
|
||||
const htmlMinify = (await import('html-minifier-terser')).minify;
|
||||
htmlContent = await htmlMinify(htmlContent, {
|
||||
caseSensitive: false,
|
||||
collapseBooleanAttributes: true,
|
||||
collapseInlineTagWhitespace: false,
|
||||
collapseWhitespace: true,
|
||||
conservativeCollapse: false,
|
||||
decodeEntities: true,
|
||||
html5: true,
|
||||
includeAutoGeneratedTags: true,
|
||||
keepClosingSlash: false,
|
||||
minifyCSS: true,
|
||||
minifyJS: true,
|
||||
minifyURLs: false,
|
||||
preserveLineBreaks: false,
|
||||
preventAttributesEscaping: false,
|
||||
processConditionalComments: false,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeEmptyElements: false,
|
||||
removeOptionalTags: true,
|
||||
removeRedundantAttributes: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
removeTagWhitespace: false,
|
||||
sortAttributes: true,
|
||||
sortClassName: true,
|
||||
trimCustomFragments: true,
|
||||
useShortDoctype: true
|
||||
});
|
||||
}
|
||||
|
||||
// Write the processed HTML file
|
||||
await fs.writeFile(path.join(paths.dist, 'index.html'), htmlContent);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Template processing (only for development builds)
|
||||
function templates() {
|
||||
if (isProduction) {
|
||||
// In production, templates are bundled into the HTML file
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return gulp.src(paths.src.html.templates)
|
||||
.pipe(gulp.dest(`${paths.dist}/templates`));
|
||||
}
|
||||
|
||||
// Language files processing
|
||||
function languages() {
|
||||
return gulp.src(paths.src.lang)
|
||||
.pipe(gulpif(isProduction, jsonMinify()))
|
||||
.pipe(gulp.dest(`${paths.dist}/lang`));
|
||||
}
|
||||
|
||||
// Copy assets (SVGs are bundled in production)
|
||||
function assets() {
|
||||
if (isProduction) {
|
||||
// In production, SVGs are bundled into the HTML file, so only copy other assets
|
||||
return gulp.src(paths.src.assets, { base: '.' })
|
||||
.pipe(gulp.dest(paths.dist));
|
||||
}
|
||||
|
||||
return gulp.src([...paths.src.assets, paths.src.svg], { base: '.' })
|
||||
.pipe(gulp.dest(paths.dist));
|
||||
}
|
||||
|
||||
// Watch task
|
||||
function watch() {
|
||||
gulp.watch(paths.src.js.all, scripts);
|
||||
gulp.watch(paths.src.css, styles);
|
||||
gulp.watch([paths.src.html.main, paths.src.html.templates], gulp.series(html, templates));
|
||||
gulp.watch(paths.src.lang, languages);
|
||||
gulp.watch([...paths.src.assets, paths.src.svg], assets);
|
||||
}
|
||||
|
||||
// Development task
|
||||
function dev() {
|
||||
console.log('🚀 Development mode - watching files for changes...');
|
||||
return watch();
|
||||
}
|
||||
|
||||
// Build task
|
||||
const build = gulp.series(
|
||||
clean,
|
||||
gulp.parallel(scripts, styles),
|
||||
bundleAssets,
|
||||
gulp.parallel(html, templates, languages, assets)
|
||||
);
|
||||
|
||||
// Export tasks
|
||||
export { clean, scripts, styles, bundleAssets, html, templates, languages, assets, watch, dev, build };
|
||||
export default build;
|
||||
16
index.html
16
index.html
@@ -314,19 +314,7 @@
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-FSXPMDXLLS');
|
||||
|
||||
// Wait for the module to load before calling gboot
|
||||
if (window.gboot) {
|
||||
gboot();
|
||||
} else {
|
||||
// If gboot isn't available yet, wait for it
|
||||
const checkGboot = () => {
|
||||
if (window.gboot) {
|
||||
gboot();
|
||||
} else {
|
||||
setTimeout(checkGboot, 10);
|
||||
}
|
||||
};
|
||||
checkGboot();
|
||||
}
|
||||
// The gboot() function is now auto-called when the core.js module loads
|
||||
// No manual initialization needed here
|
||||
</script>
|
||||
</html>
|
||||
|
||||
22
js/core.js
22
js/core.js
@@ -323,11 +323,20 @@ function welcome_accepted() {
|
||||
async function init_svg_controller() {
|
||||
const svgContainer = document.getElementById('controller-svg-placeholder');
|
||||
|
||||
const response = await fetch('assets/dualshock-controller.svg'); // load it from separate HTML file
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load controller SVG');
|
||||
let svgContent;
|
||||
|
||||
// Check if we have bundled assets (production mode)
|
||||
if (window.BUNDLED_ASSETS && window.BUNDLED_ASSETS.svg && window.BUNDLED_ASSETS.svg['dualshock-controller.svg']) {
|
||||
svgContent = window.BUNDLED_ASSETS.svg['dualshock-controller.svg'];
|
||||
} else {
|
||||
// Fallback to fetching from server (development mode)
|
||||
const response = await fetch('assets/dualshock-controller.svg');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load controller SVG');
|
||||
}
|
||||
svgContent = await response.text();
|
||||
}
|
||||
const svgContent = await response.text();
|
||||
|
||||
svgContainer.innerHTML = svgContent;
|
||||
|
||||
const lightBlue = '#7ecbff';
|
||||
@@ -863,4 +872,7 @@ window.nvslock = nvslock;
|
||||
window.welcome_accepted = welcome_accepted;
|
||||
window.show_donate_modal = show_donate_modal;
|
||||
window.board_model_info = board_model_info;
|
||||
window.edge_color_info = edge_color_info;
|
||||
window.edge_color_info = edge_color_info;
|
||||
|
||||
// Auto-initialize the application when the module loads
|
||||
gboot();
|
||||
@@ -4,7 +4,7 @@
|
||||
const templateCache = new Map();
|
||||
|
||||
/**
|
||||
* Load a template from the templates directory
|
||||
* Load a template from the templates directory or bundled assets
|
||||
* @param {string} templateName - Name of the template file without extension
|
||||
* @returns {Promise<string>} - Promise that resolves with the template HTML
|
||||
*/
|
||||
@@ -15,6 +15,16 @@ async function loadTemplate(templateName) {
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if we have bundled assets (production mode)
|
||||
if (window.BUNDLED_ASSETS && window.BUNDLED_ASSETS.templates) {
|
||||
const templateHtml = window.BUNDLED_ASSETS.templates[templateName];
|
||||
if (templateHtml) {
|
||||
templateCache.set(templateName, templateHtml);
|
||||
return templateHtml;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to fetching from server (development mode)
|
||||
// Only append .html if the templateName doesn't already have an extension
|
||||
const hasExtension = templateName.includes('.');
|
||||
const templatePath = hasExtension ? `templates/${templateName}` : `templates/${templateName}.html`;
|
||||
@@ -33,13 +43,41 @@ async function loadTemplate(templateName) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load SVG assets from bundled assets or server
|
||||
* @param {string} assetPath - Path to the SVG asset
|
||||
* @returns {Promise<string>} - Promise that resolves with the SVG content
|
||||
*/
|
||||
async function loadSvgAsset(assetPath) {
|
||||
try {
|
||||
// Check if we have bundled assets (production mode)
|
||||
if (window.BUNDLED_ASSETS && window.BUNDLED_ASSETS.svg) {
|
||||
const svgContent = window.BUNDLED_ASSETS.svg[assetPath];
|
||||
if (svgContent) {
|
||||
return svgContent;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to fetching from server (development mode)
|
||||
const response = await fetch(`assets/${assetPath}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load SVG asset: ${assetPath}`);
|
||||
}
|
||||
|
||||
return await response.text();
|
||||
} catch (error) {
|
||||
console.error(`Error loading SVG asset ${assetPath}:`, error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all templates and insert them into the DOM
|
||||
*/
|
||||
export async function loadAllTemplates() {
|
||||
try {
|
||||
// Load SVG icons
|
||||
const iconsHtml = await loadTemplate('../assets/icons.svg');
|
||||
const iconsHtml = await loadSvgAsset('icons.svg');
|
||||
const iconsContainer = document.createElement('div');
|
||||
iconsContainer.innerHTML = iconsHtml;
|
||||
document.body.prepend(iconsContainer);
|
||||
|
||||
5653
package-lock.json
generated
Normal file
5653
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
56
package.json
Normal file
56
package.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "dualshock-calibration-gui",
|
||||
"version": "1.0.0",
|
||||
"description": "A web-based calibration tool for PlayStation DualShock 4, DualSense, and DualSense Edge controllers",
|
||||
"main": "index.html",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "gulp build",
|
||||
"build:prod": "gulp build --production",
|
||||
"clean": "gulp clean",
|
||||
"dev": "gulp dev",
|
||||
"watch": "gulp watch",
|
||||
"serve": "node dev-server.js",
|
||||
"serve:https": "HTTPS=true node dev-server.js",
|
||||
"start": "npm run build && npm run serve",
|
||||
"dev:serve": "npm run build && concurrently \"npm run dev\" \"npm run serve\"",
|
||||
"dev:full": "npm run build && concurrently --kill-others \"npm run watch\" \"npm run serve\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"rollup": "^4.9.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"gulp": "^5.0.1",
|
||||
"gulp-cli": "^3.1.0",
|
||||
"gulp-terser": "^2.1.0",
|
||||
"gulp-clean-css": "^4.3.0",
|
||||
"gulp-htmlmin": "^5.0.1",
|
||||
"gulp-rev": "^11.0.0",
|
||||
"gulp-rev-replace": "^0.4.4",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-sourcemaps": "^3.0.0",
|
||||
"gulp-if": "^3.0.0",
|
||||
"gulp-rename": "^2.1.0",
|
||||
"rollup-stream": "^1.24.1",
|
||||
"vinyl-source-stream": "^2.0.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"del": "^8.0.0",
|
||||
"yargs": "^18.0.0",
|
||||
"gulp-svgmin": "^4.1.0",
|
||||
"gulp-replace": "^1.1.4",
|
||||
"gulp-json-minify": "^1.2.3",
|
||||
"glob": "^10.3.10",
|
||||
"html-minifier-terser": "^7.2.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"http-server": "^14.1.1"
|
||||
},
|
||||
"keywords": [
|
||||
"dualshock",
|
||||
"dualsense",
|
||||
"controller",
|
||||
"calibration",
|
||||
"webHID"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT"
|
||||
}
|
||||
72
setup-dev.sh
Executable file
72
setup-dev.sh
Executable file
@@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
|
||||
# DualShock Calibration GUI - Development Setup Script
|
||||
|
||||
echo "🎮 DualShock Calibration GUI - Development Setup"
|
||||
echo "================================================"
|
||||
echo ""
|
||||
|
||||
# Check if Node.js is installed
|
||||
if ! command -v node &> /dev/null; then
|
||||
echo "❌ Node.js is not installed"
|
||||
echo "💡 Please install Node.js (v16 or higher) from https://nodejs.org/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Node.js version
|
||||
NODE_VERSION=$(node -v | cut -d'v' -f2)
|
||||
MAJOR_VERSION=$(echo $NODE_VERSION | cut -d'.' -f1)
|
||||
|
||||
if [ "$MAJOR_VERSION" -lt 16 ]; then
|
||||
echo "❌ Node.js version $NODE_VERSION is too old"
|
||||
echo "💡 Please upgrade to Node.js v16 or higher"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Node.js version: $NODE_VERSION"
|
||||
|
||||
# Check if npm is installed
|
||||
if ! command -v npm &> /dev/null; then
|
||||
echo "❌ npm is not installed"
|
||||
echo "💡 npm should come with Node.js installation"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ npm version: $(npm -v)"
|
||||
echo ""
|
||||
|
||||
# Install dependencies
|
||||
echo "📦 Installing dependencies..."
|
||||
if npm install; then
|
||||
echo "✅ Dependencies installed successfully"
|
||||
else
|
||||
echo "❌ Failed to install dependencies"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Build the application
|
||||
echo "🔨 Building application..."
|
||||
if npm run build; then
|
||||
echo "✅ Application built successfully"
|
||||
else
|
||||
echo "❌ Failed to build application"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🎉 Setup complete!"
|
||||
echo ""
|
||||
echo "🚀 To start development:"
|
||||
echo " npm run dev:full"
|
||||
echo ""
|
||||
echo "📱 The app will be available at:"
|
||||
echo " https://localhost:8443"
|
||||
echo ""
|
||||
echo "💡 You may need to accept the SSL certificate warning in your browser"
|
||||
echo "💡 Use Chrome or Edge for full WebHID support"
|
||||
echo ""
|
||||
echo "📚 For more information, see:"
|
||||
echo " - README.md"
|
||||
echo " - DEVELOPMENT.md"
|
||||
Reference in New Issue
Block a user