Actually working swipe controls for pacman game

This commit is contained in:
Tyler
2025-05-01 21:49:06 -10:00
committed by GitHub
parent 1c1fed1dd5
commit 61193320d4
7 changed files with 1508 additions and 1394 deletions
+177 -177
View File
@@ -1,177 +1,177 @@
# PacCrypt WebApp # PacCrypt WebApp
**PacCrypt** is a secure, feature-rich web app for encrypting and decrypting text and files — built with Flask, JavaScript, and AES-GCM encryption. **PacCrypt** is a secure, feature-rich web app for encrypting and decrypting text and files — built with Flask, JavaScript, and AES-GCM encryption.
Now with an admin control panel, GitHub updater, and a built-in Pac-Man easter egg! 🕹️ Now with an admin control panel, GitHub updater, and a built-in Pac-Man easter egg! 🕹️
Live demo: [paccrypt.unnaturalll.dev](http://paccrypt.unnaturalll.dev) Live demo: [paccrypt.unnaturalll.dev](http://paccrypt.unnaturalll.dev)
--- ---
## ✨ Features ## ✨ Features
- 🔒 Basic and Advanced Encryption for Text & Files - 🔒 Basic and Advanced Encryption for Text & Files
- 📁 Secure File Uploads with Pickup Passwords - 📁 Secure File Uploads with Pickup Passwords
- 🔑 Random Password Generator - 🔑 Random Password Generator
- 🎮 Hidden Pac-Man Game — type `pacman` to play - 🎮 Hidden Pac-Man Game — type `pacman` to play
- 🧠 Smart UI: Auto-switches input sections, toggles encryption labels - 🧠 Smart UI: Auto-switches input sections, toggles encryption labels
- 📋 Clipboard Copy Feedback with styled status boxes - 📋 Clipboard Copy Feedback with styled status boxes
- 🧾 Admin Panel: - 🧾 Admin Panel:
- Site map with live route list - Site map with live route list
- Server restart & GitHub update button - Server restart & GitHub update button
- Secure admin credential management - Secure admin credential management
- Server logs & upload cleanup - Server logs & upload cleanup
- 🧩 System Settings Page for upload config - 🧩 System Settings Page for upload config
- 📜 Custom 403, 404, and 500 Error Pages - 📜 Custom 403, 404, and 500 Error Pages
- 🤖 robots.txt and /sitemap for crawlers - 🤖 robots.txt and /sitemap for crawlers
- 📱 Mobile-Responsive UI - 📱 Mobile-Responsive UI
--- ---
## 👨‍💻 Installation ## 👨‍💻 Installation
### 📋 Prerequisites ### 📋 Prerequisites
- Python 3.7+ - Python 3.7+
- Flask 3+ - Flask 3+
- Cryptography 42+ - Cryptography 42+
- Waitress 2.1+ - Waitress 2.1+
- Git (for update feature) - Git (for update feature)
- Nginx (recommended) - Nginx (recommended)
--- ---
### ⚡ Quick Setup ### ⚡ Quick Setup
```bash ```bash
git clone https://github.com/TySP-Dev/PacCrypt.git git clone https://github.com/TySP-Dev/PacCrypt.git
cd paccrypt-webapp-final cd paccrypt-webapp-final
python -m venv venv python -m venv venv
source venv/bin/activate # or venv\Scripts\activate on Windows source venv/bin/activate # or venv\Scripts\activate on Windows
pip install -r requirements.txt pip install -r requirements.txt
``` ```
Then run: Then run:
- Development Mode: - Development Mode:
```bash ```bash
./start_dev.sh # or start_dev.bat ./start_dev.sh # or start_dev.bat
``` ```
- Production Mode: - Production Mode:
```bash ```bash
./start_prod.sh # or start_prod.bat ./start_prod.sh # or start_prod.bat
``` ```
Visit [http://127.0.0.1:5000](http://127.0.0.1:5000) Visit [http://127.0.0.1:5000](http://127.0.0.1:5000)
--- ---
## 🧭 Navigation & Usage ## 🧭 Navigation & Usage
### 🔐 Encrypt & Decrypt ### 🔐 Encrypt & Decrypt
- Choose between Basic Cipher or Advanced AES - Choose between Basic Cipher or Advanced AES
- Type your message or upload a file - Type your message or upload a file
- Enter password (if AES) - Enter password (if AES)
- Select mode using toggle (Encrypt/Decrypt) - Select mode using toggle (Encrypt/Decrypt)
- Hit Execute - Hit Execute
### 📤 Share Files ### 📤 Share Files
- Upload a file with two passwords: - Upload a file with two passwords:
- Encryption password - Encryption password
- Pickup password - Pickup password
- Get a shareable URL and click 📋 Copy Link - Get a shareable URL and click 📋 Copy Link
### 🔑 Generate Passwords ### 🔑 Generate Passwords
- Click Generate - Click Generate
- Then hit 📋 Copy - Then hit 📋 Copy
### 🎮 Pac-Man Game ### 🎮 Pac-Man Game
- Type `pacman` in the input box - Type `pacman` in the input box
- Game appears with Restart/Exit controls - Game appears with Restart/Exit controls
- Classic arrow key controls 🕹️ - Classic arrow key controls 🕹️
--- ---
## 🛠️ Admin Panel ## 🛠️ Admin Panel
Visit `/adminpage` after setting up credentials at `/admin-setup`. Visit `/adminpage` after setting up credentials at `/admin-setup`.
Features: Features:
- 🔄 Restart server - 🔄 Restart server
- 🔃 Update from GitHub (git pull) - 🔃 Update from GitHub (git pull)
- 🧽 Clear uploads - 🧽 Clear uploads
- 🔐 Change admin password - 🔐 Change admin password
- 📝 View logs - 📝 View logs
- ⚙️ Adjust upload settings - ⚙️ Adjust upload settings
--- ---
## 🛡️ Deployment Tips ## 🛡️ Deployment Tips
Minimal Nginx config: Minimal Nginx config:
```nginx ```nginx
server { server {
listen 80; listen 80;
server_name yourdomain.com; server_name yourdomain.com;
location / { location / {
proxy_pass http://127.0.0.1:5000; proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
} }
} }
``` ```
Use Let's Encrypt to add SSL/TLS support. Use Let's Encrypt to add SSL/TLS support.
--- ---
## 🗂️ Project Structure ## 🗂️ Project Structure
``` ```
paccrypt-webapp-final/ paccrypt-webapp-final/
├── app.py ├── app.py
├── requirements.txt ├── requirements.txt
├── README.md ├── README.md
├── templates/ ├── templates/
│ ├── index.html │ ├── index.html
│ ├── 404.html │ ├── 404.html
│ └── 403.html │ └── 403.html
│ └── 500.html │ └── 500.html
│ └── admin.html │ └── admin.html
│ └── admin_login.html │ └── admin_login.html
│ └── admin_settings.html │ └── admin_settings.html
│ └── admin_setup.html │ └── admin_setup.html
│ └── pickup.html │ └── pickup.html
├── static/ ├── static/
│ ├── css/ │ ├── css/
│ │ └── styles.css │ │ └── styles.css
│ ├── js/ │ ├── js/
│ │ └── ui.js │ │ └── ui.js
│ │ └── pacman.js │ │ └── pacman.js
│ │ └── main.js │ │ └── main.js
│ │ └── fileops.js │ │ └── fileops.js
│ │ └── encryption.js │ │ └── encryption.js
│ ├── img/ │ ├── img/
│ │ └── PacCrypt.png │ │ └── PacCrypt.png
│ │ └── Github_logo.png │ │ └── Github_logo.png
│ │ └── sitemap.png │ │ └── sitemap.png
│ └── audio/ │ └── audio/
│ └── chomp.mp3 │ └── chomp.mp3
├── start_dev.bat ├── start_dev.bat
├── start_prod.bat ├── start_prod.bat
├── start_dev.sh ├── start_dev.sh
├── start_prod.sh ├── start_prod.sh
``` ```
--- ---
## 📄 License ## 📄 License
MIT © [TySP-Dev](https://github.com/TySP-Dev) MIT © [TySP-Dev](https://github.com/TySP-Dev)
+719 -714
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -1,9 +1,9 @@
### **requirements.txt** ### **requirements.txt**
flask==3.0.3 flask==3.0.3
cryptography==42.0.5 cryptography==42.0.5
waitress==2.1.2 waitress==2.1.2
werkzeug==3.0.1 werkzeug==3.0.1
# nginx - Only needed for Nginx integration, not installed via pip # nginx - Only needed for Nginx integration, not installed via pip
# Run pip install -r requirements.txt # Run pip install -r requirements.txt
+104 -104
View File
@@ -1,104 +1,104 @@
/** /**
* Encryption module. * Encryption module.
* Handles cryptographic operations using Web Crypto API. * Handles cryptographic operations using Web Crypto API.
* Implements AES-GCM encryption with PBKDF2 key derivation. * Implements AES-GCM encryption with PBKDF2 key derivation.
*/ */
// ===== Constants ===== // ===== Constants =====
const SALT_LENGTH = 16; const SALT_LENGTH = 16;
const IV_LENGTH = 12; const IV_LENGTH = 12;
const PBKDF2_ITERATIONS = 200_000; const PBKDF2_ITERATIONS = 200_000;
const KEY_LENGTH = 256; const KEY_LENGTH = 256;
// ===== Key Derivation ===== // ===== Key Derivation =====
/** /**
* Derives an AES-GCM key from a password using PBKDF2. * Derives an AES-GCM key from a password using PBKDF2.
* @param {string} password - User-supplied password. * @param {string} password - User-supplied password.
* @param {Uint8Array} salt - Randomly generated salt. * @param {Uint8Array} salt - Randomly generated salt.
* @returns {Promise<CryptoKey>} - Derived cryptographic key. * @returns {Promise<CryptoKey>} - Derived cryptographic key.
*/ */
export async function deriveKey(password, salt) { export async function deriveKey(password, salt) {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey( const keyMaterial = await crypto.subtle.importKey(
'raw', 'raw',
encoder.encode(password), encoder.encode(password),
{ name: 'PBKDF2' }, { name: 'PBKDF2' },
false, false,
['deriveKey'] ['deriveKey']
); );
return crypto.subtle.deriveKey( return crypto.subtle.deriveKey(
{ {
name: 'PBKDF2', name: 'PBKDF2',
salt, salt,
iterations: PBKDF2_ITERATIONS, iterations: PBKDF2_ITERATIONS,
hash: 'SHA-256' hash: 'SHA-256'
}, },
keyMaterial, keyMaterial,
{ name: 'AES-GCM', length: KEY_LENGTH }, { name: 'AES-GCM', length: KEY_LENGTH },
false, false,
['encrypt', 'decrypt'] ['encrypt', 'decrypt']
); );
} }
// ===== Encryption ===== // ===== Encryption =====
/** /**
* Encrypts a message using AES-GCM with a derived key. * Encrypts a message using AES-GCM with a derived key.
* @param {string} message - Plaintext message to encrypt. * @param {string} message - Plaintext message to encrypt.
* @param {string} password - User password for key derivation. * @param {string} password - User password for key derivation.
* @returns {Promise<string>} - Base64-encoded encrypted string. * @returns {Promise<string>} - Base64-encoded encrypted string.
*/ */
export async function encryptAdvanced(message, password) { export async function encryptAdvanced(message, password) {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH)); const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH));
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH)); const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
const key = await deriveKey(password, salt); const key = await deriveKey(password, salt);
const encoded = encoder.encode(message); const encoded = encoder.encode(message);
const ciphertext = await crypto.subtle.encrypt( const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv }, { name: 'AES-GCM', iv },
key, key,
encoded encoded
); );
const output = new Uint8Array(salt.length + iv.length + ciphertext.byteLength); const output = new Uint8Array(salt.length + iv.length + ciphertext.byteLength);
output.set(salt); output.set(salt);
output.set(iv, salt.length); output.set(iv, salt.length);
output.set(new Uint8Array(ciphertext), salt.length + iv.length); output.set(new Uint8Array(ciphertext), salt.length + iv.length);
return btoa(String.fromCharCode(...output)); return btoa(String.fromCharCode(...output));
} }
// ===== Decryption ===== // ===== Decryption =====
/** /**
* Decrypts an AES-GCM encrypted string. * Decrypts an AES-GCM encrypted string.
* @param {string} encryptedData - Base64-encoded ciphertext. * @param {string} encryptedData - Base64-encoded ciphertext.
* @param {string} password - Password used to derive the decryption key. * @param {string} password - Password used to derive the decryption key.
* @returns {Promise<string>} - Decrypted plaintext. * @returns {Promise<string>} - Decrypted plaintext.
*/ */
export async function decryptAdvanced(encryptedData, password) { export async function decryptAdvanced(encryptedData, password) {
const encrypted = new Uint8Array( const encrypted = new Uint8Array(
atob(encryptedData).split('').map(c => c.charCodeAt(0)) atob(encryptedData).split('').map(c => c.charCodeAt(0))
); );
const salt = encrypted.slice(0, SALT_LENGTH); const salt = encrypted.slice(0, SALT_LENGTH);
const iv = encrypted.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH); const iv = encrypted.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
const ciphertext = encrypted.slice(SALT_LENGTH + IV_LENGTH); const ciphertext = encrypted.slice(SALT_LENGTH + IV_LENGTH);
const key = await deriveKey(password, salt); const key = await deriveKey(password, salt);
const decrypted = await crypto.subtle.decrypt( const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv }, { name: 'AES-GCM', iv },
key, key,
ciphertext ciphertext
); );
return new TextDecoder().decode(decrypted); return new TextDecoder().decode(decrypted);
} }
// ===== Module Initialization ===== // ===== Module Initialization =====
/** /**
* Initializes the encryption module and logs its status. * Initializes the encryption module and logs its status.
*/ */
export function setupEncryption() { export function setupEncryption() {
console.log('[Encryption] Module loaded'); console.log('[Encryption] Module loaded');
} }
+119 -119
View File
@@ -1,119 +1,119 @@
/** /**
* File operations module. * File operations module.
* Handles file encryption and decryption operations. * Handles file encryption and decryption operations.
*/ */
// ===== Constants ===== // ===== Constants =====
const CHUNK_SIZE = 1024 * 1024; // 1MB chunks const CHUNK_SIZE = 1024 * 1024; // 1MB chunks
// ===== Public Interface ===== // ===== Public Interface =====
export async function encryptFile(fileInput, password) { export async function encryptFile(fileInput, password) {
const file = fileInput.files[0]; const file = fileInput.files[0];
if (!file) return; if (!file) return;
try { try {
const encryptedChunks = await processFile(file, password, true); const encryptedChunks = await processFile(file, password, true);
downloadEncryptedFile(encryptedChunks, file.name); downloadEncryptedFile(encryptedChunks, file.name);
} catch (error) { } catch (error) {
alert("Error encrypting file: " + error.message); alert("Error encrypting file: " + error.message);
} }
} }
export async function decryptFile(fileInput, password) { export async function decryptFile(fileInput, password) {
const file = fileInput.files[0]; const file = fileInput.files[0];
if (!file) return; if (!file) return;
try { try {
const decryptedChunks = await processFile(file, password, false); const decryptedChunks = await processFile(file, password, false);
downloadDecryptedFile(decryptedChunks, file.name); downloadDecryptedFile(decryptedChunks, file.name);
} catch (error) { } catch (error) {
alert("Error decrypting file: " + error.message); alert("Error decrypting file: " + error.message);
} }
} }
// ===== File Processing ===== // ===== File Processing =====
async function processFile(file, password, isEncrypt) { async function processFile(file, password, isEncrypt) {
const chunks = []; const chunks = [];
const totalChunks = Math.ceil(file.size / CHUNK_SIZE); const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
let processedChunks = 0; let processedChunks = 0;
for (let start = 0; start < file.size; start += CHUNK_SIZE) { for (let start = 0; start < file.size; start += CHUNK_SIZE) {
const chunk = file.slice(start, start + CHUNK_SIZE); const chunk = file.slice(start, start + CHUNK_SIZE);
const arrayBuffer = await chunk.arrayBuffer(); const arrayBuffer = await chunk.arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer); const uint8Array = new Uint8Array(arrayBuffer);
const processedChunk = await processChunk(uint8Array, password, isEncrypt); const processedChunk = await processChunk(uint8Array, password, isEncrypt);
chunks.push(processedChunk); chunks.push(processedChunk);
processedChunks++; processedChunks++;
updateProgress(processedChunks, totalChunks); updateProgress(processedChunks, totalChunks);
} }
return chunks; return chunks;
} }
async function processChunk(data, password, isEncrypt) { async function processChunk(data, password, isEncrypt) {
const payload = { const payload = {
"encryption-type": "advanced", "encryption-type": "advanced",
operation: isEncrypt ? "encrypt" : "decrypt", operation: isEncrypt ? "encrypt" : "decrypt",
message: Array.from(data).join(','), message: Array.from(data).join(','),
password: password password: password
}; };
const response = await fetch("/", { const response = await fetch("/", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload) body: JSON.stringify(payload)
}); });
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`); throw new Error(`HTTP error! status: ${response.status}`);
} }
const result = await response.json(); const result = await response.json();
return new Uint8Array(result.result.split(',').map(Number)); return new Uint8Array(result.result.split(',').map(Number));
} }
// ===== File Download ===== // ===== File Download =====
function downloadEncryptedFile(chunks, originalName) { function downloadEncryptedFile(chunks, originalName) {
const blob = new Blob(chunks, { type: 'application/octet-stream' }); const blob = new Blob(chunks, { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement('a');
a.href = url; a.href = url;
a.download = originalName + '.encrypted'; a.download = originalName + '.encrypted';
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
document.body.removeChild(a); document.body.removeChild(a);
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} }
function downloadDecryptedFile(chunks, originalName) { function downloadDecryptedFile(chunks, originalName) {
const blob = new Blob(chunks, { type: 'application/octet-stream' }); const blob = new Blob(chunks, { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement('a');
a.href = url; a.href = url;
a.download = originalName.replace('.encrypted', ''); a.download = originalName.replace('.encrypted', '');
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
document.body.removeChild(a); document.body.removeChild(a);
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} }
// ===== Progress Tracking ===== // ===== Progress Tracking =====
function updateProgress(processed, total) { function updateProgress(processed, total) {
const progressBar = document.getElementById("file-progress"); const progressBar = document.getElementById("file-progress");
const progressText = document.getElementById("file-progress-text"); const progressText = document.getElementById("file-progress-text");
if (progressBar && progressText) { if (progressBar && progressText) {
const percent = Math.round((processed / total) * 100); const percent = Math.round((processed / total) * 100);
progressBar.style.width = percent + "%"; progressBar.style.width = percent + "%";
progressText.textContent = `Processing: ${percent}%`; progressText.textContent = `Processing: ${percent}%`;
if (processed === total) { if (processed === total) {
setTimeout(() => { setTimeout(() => {
progressBar.style.width = "0%"; progressBar.style.width = "0%";
progressText.textContent = ""; progressText.textContent = "";
}, 1000); }, 1000);
} }
} }
} }
+80
View File
@@ -21,6 +21,13 @@ export function setupGame() {
} }
export function startPacman() { export function startPacman() {
// Scroll to the Pacman section
const pacmanSection = document.getElementById("pacman-section");
if (pacmanSection) {
pacmanSection.scrollIntoView({ behavior: 'smooth' });
}
// Initialize game state
initializeGame(); initializeGame();
setupGameLoop(); setupGameLoop();
} }
@@ -28,6 +35,11 @@ export function startPacman() {
export function stopPacman() { export function stopPacman() {
clearInterval(gameInterval); clearInterval(gameInterval);
if (ctx) ctx.clearRect(0, 0, canvas.width, canvas.height); if (ctx) ctx.clearRect(0, 0, canvas.width, canvas.height);
// Restore scrolling
document.body.style.overflow = '';
document.removeEventListener('wheel', preventScroll);
document.removeEventListener('touchmove', preventScroll);
} }
export function resetGame() { export function resetGame() {
@@ -68,6 +80,55 @@ function initializeGame() {
pacman.dx = pacman.dy = 0; pacman.dx = pacman.dy = 0;
document.addEventListener("keydown", movePacman); document.addEventListener("keydown", movePacman);
// Prevent scrolling
document.body.style.overflow = 'hidden';
document.addEventListener('wheel', preventScroll, { passive: false });
document.addEventListener('touchmove', preventScroll, { passive: false });
// Add touch controls
let touchStartX = 0;
let touchStartY = 0;
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
}, { passive: false });
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
}, { passive: false });
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
const touchEndX = e.changedTouches[0].clientX;
const touchEndY = e.changedTouches[0].clientY;
const dx = touchEndX - touchStartX;
const dy = touchEndY - touchStartY;
// Determine swipe direction based on the larger movement
if (Math.abs(dx) > Math.abs(dy)) {
// Horizontal swipe
if (dx > 0) {
pacman.dx = PACMAN_SPEED;
pacman.dy = 0;
} else {
pacman.dx = -PACMAN_SPEED;
pacman.dy = 0;
}
} else {
// Vertical swipe
if (dy > 0) {
pacman.dx = 0;
pacman.dy = PACMAN_SPEED;
} else {
pacman.dx = 0;
pacman.dy = -PACMAN_SPEED;
}
}
}, { passive: false });
} }
function setupGameLoop() { function setupGameLoop() {
@@ -283,6 +344,20 @@ function eatDots() {
return true; return true;
}); });
// Check if all dots are eaten
if (dots.length === 0) {
// Trigger password generator for new random map
const generateBtn = document.getElementById("generate-btn");
if (generateBtn) {
generateBtn.click();
}
// Auto-restart the game after a short delay
setTimeout(() => {
resetGame();
}, 1000);
}
ctx.fillStyle = "white"; ctx.fillStyle = "white";
dots.forEach(d => { dots.forEach(d => {
ctx.beginPath(); ctx.beginPath();
@@ -294,3 +369,8 @@ function eatDots() {
// ===== Global Functions ===== // ===== Global Functions =====
window.resetGame = resetGame; window.resetGame = resetGame;
window.exitGame = exitGame; window.exitGame = exitGame;
// Add scroll prevention function
function preventScroll(e) {
e.preventDefault();
}
+301 -272
View File
@@ -1,272 +1,301 @@
/** /**
* UI management module. * UI management module.
* Handles user interface interactions and form handling. * Handles user interface interactions and form handling.
*/ */
import { encryptFile, decryptFile } from './fileops.js'; import { encryptFile, decryptFile } from './fileops.js';
// ===== UI Initialization ===== // ===== UI Initialization =====
export function setupUI() { export function setupUI() {
// Set initial state of remove button to hidden // Set initial state of remove button to hidden
const removeBtn = document.getElementById("remove-file-btn"); const removeBtn = document.getElementById("remove-file-btn");
if (removeBtn) { if (removeBtn) {
removeBtn.style.display = "none"; removeBtn.style.display = "none";
} }
initializeEventListeners(); initializeEventListeners();
} }
// ===== Event Listeners ===== // ===== Event Listeners =====
function initializeEventListeners() { function initializeEventListeners() {
const elements = { const elements = {
encryptionType: document.getElementById("encryption-type"), encryptionType: document.getElementById("encryption-type"),
inputText: document.getElementById("input-text"), inputText: document.getElementById("input-text"),
form: document.getElementById("crypto-form"), form: document.getElementById("crypto-form"),
removeFileBtn: document.getElementById("remove-file-btn"), removeFileBtn: document.getElementById("remove-file-btn"),
clearAllBtn: document.getElementById("clear-all-btn"), clearAllBtn: document.getElementById("clear-all-btn"),
generateBtn: document.getElementById("generate-btn"), generateBtn: document.getElementById("generate-btn"),
copyPasswordBtn: document.getElementById("copy-btn"), copyPasswordBtn: document.getElementById("copy-btn"),
copyOutputBtn: document.getElementById("copy-output-btn"), copyOutputBtn: document.getElementById("copy-output-btn"),
toggleSwitch: document.getElementById("operation-toggle"), toggleSwitch: document.getElementById("operation-toggle"),
copyShareBtn: document.getElementById("copy-share-btn"), copyShareBtn: document.getElementById("copy-share-btn"),
shareLink: document.getElementById("share-link") shareLink: document.getElementById("share-link")
}; };
if (validateElements(elements)) { if (validateElements(elements)) {
setupElementListeners(elements); setupElementListeners(elements);
} }
} }
function validateElements(elements) { function validateElements(elements) {
return elements.encryptionType && elements.inputText && elements.form && return elements.encryptionType && elements.inputText && elements.form &&
elements.removeFileBtn && elements.clearAllBtn && elements.generateBtn && elements.removeFileBtn && elements.clearAllBtn && elements.generateBtn &&
elements.copyPasswordBtn && elements.toggleSwitch; elements.copyPasswordBtn && elements.toggleSwitch;
} }
function setupElementListeners(elements) { function setupElementListeners(elements) {
elements.encryptionType.addEventListener("change", toggleEncryptionOptions); elements.encryptionType.addEventListener("change", toggleEncryptionOptions);
elements.inputText.addEventListener("input", handleInputChange); elements.inputText.addEventListener("input", handleInputChange);
elements.form.addEventListener("submit", handleSubmit); elements.form.addEventListener("submit", handleSubmit);
elements.removeFileBtn.addEventListener("click", removeFile); elements.removeFileBtn.addEventListener("click", removeFile);
elements.clearAllBtn.addEventListener("click", clearAll); elements.clearAllBtn.addEventListener("click", clearAll);
elements.generateBtn.addEventListener("click", generateRandomPassword); elements.generateBtn.addEventListener("click", generateRandomPassword);
elements.copyPasswordBtn.addEventListener("click", () => copyToClipboard("generated-password", "password-copy-feedback")); elements.copyPasswordBtn.addEventListener("click", () => copyToClipboard("generated-password", "password-copy-feedback"));
elements.copyOutputBtn?.addEventListener("click", () => copyToClipboard("output-text", "output-copy-feedback")); elements.copyOutputBtn?.addEventListener("click", () => copyToClipboard("output-text", "output-copy-feedback"));
elements.toggleSwitch.addEventListener("change", updateToggleLabels); elements.toggleSwitch.addEventListener("change", updateToggleLabels);
// Add file input change listener // Add file input change listener
const fileInput = document.getElementById("file-input"); const fileInput = document.getElementById("file-input");
if (fileInput) { if (fileInput) {
fileInput.addEventListener("change", () => { fileInput.addEventListener("change", () => {
const removeBtn = document.getElementById("remove-file-btn"); const removeBtn = document.getElementById("remove-file-btn");
if (removeBtn) { if (removeBtn) {
removeBtn.style.display = fileInput.files.length > 0 ? "inline-block" : "none"; removeBtn.style.display = fileInput.files.length > 0 ? "inline-block" : "none";
} }
}); });
} }
setupShareLinkListeners(elements); setupShareLinkListeners(elements);
} }
function setupShareLinkListeners(elements) { function setupShareLinkListeners(elements) {
if (elements.copyShareBtn && elements.shareLink) { if (elements.copyShareBtn && elements.shareLink) {
elements.copyShareBtn.addEventListener("click", () => { elements.copyShareBtn.addEventListener("click", () => {
const linkText = elements.shareLink.textContent.trim(); const linkText = elements.shareLink.textContent.trim();
navigator.clipboard.writeText(linkText).then(() => { navigator.clipboard.writeText(linkText).then(() => {
const feedback = document.getElementById("shared-link-feedback"); const feedback = document.getElementById("shared-link-feedback");
if (feedback) { if (feedback) {
feedback.style.display = "block"; feedback.style.display = "block";
feedback.classList.add("show"); feedback.classList.add("show");
setTimeout(() => { setTimeout(() => {
feedback.classList.remove("show"); feedback.classList.remove("show");
setTimeout(() => { setTimeout(() => {
feedback.style.display = "none"; feedback.style.display = "none";
}, 300); }, 300);
}, 3000); }, 3000);
} }
}); });
}); });
} }
} }
// ===== UI State Management ===== // ===== UI State Management =====
function toggleEncryptionOptions() { function toggleEncryptionOptions() {
const type = document.getElementById("encryption-type").value.trim().toLowerCase(); const type = document.getElementById("encryption-type").value.trim().toLowerCase();
const passwordInputWrapper = document.getElementById("password-input"); const passwordInputWrapper = document.getElementById("password-input");
const fileSection = document.querySelector("#encoding-section #file-section"); const fileSection = document.querySelector("#encoding-section #file-section");
const isAdvanced = type.includes("advanced"); const isAdvanced = type.includes("advanced");
if (passwordInputWrapper) { if (passwordInputWrapper) {
if (isAdvanced) { if (isAdvanced) {
passwordInputWrapper.classList.remove("hidden"); passwordInputWrapper.classList.remove("hidden");
} else { } else {
passwordInputWrapper.classList.add("hidden"); passwordInputWrapper.classList.add("hidden");
} }
} }
if (fileSection) { if (fileSection) {
if (isAdvanced) { if (isAdvanced) {
fileSection.classList.remove("hidden"); fileSection.classList.remove("hidden");
} else { } else {
fileSection.classList.add("hidden"); fileSection.classList.add("hidden");
} }
} }
updateToggleLabels(); updateToggleLabels();
toggleInputMode(); toggleInputMode();
} }
function updateToggleLabels() { function updateToggleLabels() {
const type = document.getElementById("encryption-type")?.value; const type = document.getElementById("encryption-type")?.value;
const leftLabel = document.getElementById("toggle-left-label"); const leftLabel = document.getElementById("toggle-left-label");
const rightLabel = document.getElementById("toggle-right-label"); const rightLabel = document.getElementById("toggle-right-label");
if (!type || !leftLabel || !rightLabel) return; if (!type || !leftLabel || !rightLabel) return;
const isAdvanced = type.toLowerCase().includes("advanced"); const isAdvanced = type.toLowerCase().includes("advanced");
leftLabel.textContent = isAdvanced ? "Encrypt" : "Encode"; leftLabel.textContent = isAdvanced ? "Encrypt" : "Encode";
rightLabel.textContent = isAdvanced ? "Decrypt" : "Decode"; rightLabel.textContent = isAdvanced ? "Decrypt" : "Decode";
} }
function toggleInputMode() { function toggleInputMode() {
const fileInput = document.getElementById("file-input"); const fileInput = document.getElementById("file-input");
const textValue = document.getElementById("input-text")?.value.trim(); const textValue = document.getElementById("input-text")?.value.trim();
const isAdvanced = document.getElementById("encryption-type")?.value === "advanced"; const isAdvanced = document.getElementById("encryption-type")?.value === "advanced";
const textSection = document.getElementById("text-section"); const textSection = document.getElementById("text-section");
const fileSection = document.getElementById("file-section"); const fileSection = document.getElementById("file-section");
const removeBtn = document.getElementById("remove-file-btn"); const removeBtn = document.getElementById("remove-file-btn");
if (!fileInput || !textSection || !fileSection || !removeBtn) return; if (!fileInput || !textSection || !fileSection || !removeBtn) return;
const fileSelected = fileInput.files.length > 0; const fileSelected = fileInput.files.length > 0;
textSection.style.display = fileSelected ? "none" : "flex"; textSection.style.display = fileSelected ? "none" : "flex";
fileSection.style.display = (isAdvanced && !textValue) ? "flex" : "none"; fileSection.style.display = (isAdvanced && !textValue) ? "flex" : "none";
removeBtn.style.display = fileSelected ? "inline-block" : "none"; removeBtn.style.display = fileSelected ? "inline-block" : "none";
} }
// ===== Form Handling ===== // ===== Form Handling =====
async function handleSubmit(event) { async function handleSubmit(event) {
event.preventDefault(); event.preventDefault();
const encryptionType = document.getElementById("encryption-type")?.value; const encryptionType = document.getElementById("encryption-type")?.value;
const password = document.getElementById("password")?.value; const password = document.getElementById("password")?.value;
const fileInput = document.getElementById("file-input"); const fileInput = document.getElementById("file-input");
const isDecrypt = document.getElementById("operation-toggle").checked; const isDecrypt = document.getElementById("operation-toggle").checked;
const operation = isDecrypt ? "decrypt" : "encrypt"; const operation = isDecrypt ? "decrypt" : "encrypt";
if (!encryptionType || !fileInput) return; if (!encryptionType || !fileInput) return;
if (encryptionType === "advanced" && !password) { if (encryptionType === "advanced" && !password) {
return alert("Password is required for advanced encryption."); return alert("Password is required for advanced encryption.");
} }
if (fileInput.files.length > 0) { if (fileInput.files.length > 0) {
return (operation === "encrypt") return (operation === "encrypt")
? encryptFile(fileInput, password) ? encryptFile(fileInput, password)
: decryptFile(fileInput, password); : decryptFile(fileInput, password);
} }
await handleTextOperation(encryptionType, operation, password); await handleTextOperation(encryptionType, operation, password);
} }
async function handleTextOperation(encryptionType, operation, password) { async function handleTextOperation(encryptionType, operation, password) {
const payload = { const payload = {
"encryption-type": encryptionType, "encryption-type": encryptionType,
operation: operation, operation: operation,
message: document.getElementById("input-text")?.value, message: document.getElementById("input-text")?.value,
password: password password: password
}; };
try { try {
const response = await fetch("/", { const response = await fetch("/", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload) body: JSON.stringify(payload)
}); });
const data = await response.json(); const data = await response.json();
document.getElementById("output-text").value = data.result; document.getElementById("output-text").value = data.result;
} catch (err) { } catch (err) {
alert("Error processing request: " + err.message); alert("Error processing request: " + err.message);
} }
} }
// ===== Utility Functions ===== // ===== Utility Functions =====
function removeFile() { function removeFile() {
const fileInput = document.getElementById("file-input"); const fileInput = document.getElementById("file-input");
if (fileInput) fileInput.value = ""; if (fileInput) fileInput.value = "";
const removeBtn = document.getElementById("remove-file-btn"); const removeBtn = document.getElementById("remove-file-btn");
if (removeBtn) removeBtn.style.display = 'none'; if (removeBtn) removeBtn.style.display = 'none';
toggleInputMode(); toggleInputMode();
} }
function generateRandomPassword() { function generateRandomPassword() {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+[]{}|;:,.<>?/~"; const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+[]{}|;:,.<>?/~";
const length = 30; const length = 30;
const password = Array.from({ length }, () => const password = Array.from({ length }, () =>
charset.charAt(Math.floor(Math.random() * charset.length)) charset.charAt(Math.floor(Math.random() * charset.length))
).join(""); ).join("");
const passwordField = document.getElementById("generated-password"); const passwordField = document.getElementById("generated-password");
if (passwordField) { if (passwordField) {
passwordField.value = password; passwordField.value = password;
// Check if we should start Pacman // Check if we should start Pacman
checkForPacman(); checkForPacman();
} }
} }
function copyToClipboard(elementId, feedbackId) { function copyToClipboard(elementId, feedbackId) {
const el = document.getElementById(elementId); const el = document.getElementById(elementId);
const feedback = document.getElementById(feedbackId); const feedback = document.getElementById(feedbackId);
if (!el || !el.value) return; if (!el || !el.value) return;
navigator.clipboard.writeText(el.value).then(() => { // Create a temporary textarea element
if (feedback) { const textarea = document.createElement('textarea');
feedback.style.display = "block"; textarea.value = el.value;
feedback.classList.add("show"); textarea.style.position = 'fixed';
setTimeout(() => { textarea.style.opacity = '0';
feedback.classList.remove("show"); document.body.appendChild(textarea);
setTimeout(() => {
feedback.style.display = "none"; // Select and copy the text
}, 300); // Wait for fade-out animation to complete textarea.select();
}, 3000); textarea.setSelectionRange(0, 99999); // For mobile devices
}
}); try {
} // Try using the modern clipboard API first
navigator.clipboard.writeText(el.value).then(() => {
function clearAll() { showFeedback(feedback);
const fields = ["input-text", "output-text", "file-input", "password"]; }).catch(() => {
fields.forEach(id => { // Fallback to execCommand for older browsers
const el = document.getElementById(id); document.execCommand('copy');
if (el) el.value = ""; showFeedback(feedback);
}); });
removeFile(); } catch (err) {
toggleInputMode(); // Final fallback
document.getElementById("pacman-section")?.style.setProperty("display", "none"); document.execCommand('copy');
document.getElementById("encoding-section")?.style.setProperty("display", "block"); showFeedback(feedback);
} }
function handleInputChange() { // Clean up
toggleInputMode(); document.body.removeChild(textarea);
checkForPacman(); }
}
function showFeedback(feedback) {
function checkForPacman() { if (feedback) {
const val = document.getElementById("input-text").value.trim().toLowerCase(); feedback.style.display = "block";
const pacSection = document.getElementById("pacman-section"); feedback.classList.add("show");
const encSection = document.getElementById("encoding-section"); setTimeout(() => {
feedback.classList.remove("show");
if (val.includes("pacman") && pacSection.style.display !== "block") { setTimeout(() => {
pacSection.style.display = "block"; feedback.style.display = "none";
encSection.style.display = "none"; }, 300);
window.startPacman(); }, 3000);
} else if (pacSection.style.display === "block" && !val.includes("pacman")) { }
window.exitGame(); }
}
} function clearAll() {
const fields = ["input-text", "output-text", "file-input", "password"];
function startPacman() { } fields.forEach(id => {
function exitGame() { } const el = document.getElementById(id);
if (el) el.value = "";
});
removeFile();
toggleInputMode();
document.getElementById("pacman-section")?.style.setProperty("display", "none");
document.getElementById("encoding-section")?.style.setProperty("display", "block");
}
function handleInputChange() {
toggleInputMode();
checkForPacman();
}
function checkForPacman() {
const val = document.getElementById("input-text").value.trim().toLowerCase();
const pacSection = document.getElementById("pacman-section");
const encSection = document.getElementById("encoding-section");
if (val.includes("pacman") && pacSection.style.display !== "block") {
pacSection.style.display = "block";
encSection.style.display = "none";
window.startPacman();
} else if (pacSection.style.display === "block" && !val.includes("pacman")) {
window.exitGame();
}
}
function startPacman() { }
function exitGame() { }