Major Changes: - 🪟 Popup Menu System: Converted slide-down navigation to draggable, minimizable popup window - ❓ Help Bubble: Added contextual help system for admin dashboard (accessible via ? bubble) - 🎨 Universal Theme Engine: All elements now properly match selected theme across all panels - 📱 Enhanced Tablet Support: Comprehensive touch gestures, handwriting recognition, and Apple Pencil optimization - 🖊️ Unified Write Mode: Combined handwriting and keyboard input with large canvas interface Technical Implementation: - Draggable popup with minimize/restore functionality and boundary constraints - Dynamic theme switching using CSS variables throughout all components - Help system with modal overlay and keyboard shortcuts (ESC to close) - Responsive design optimizations for mobile, tablet, and desktop - Comprehensive CSS overrides to eliminate hardcoded colors - JavaScript fixes for canvas stroke colors and input styling UI/UX Improvements: - Clean admin dashboard without instruction clutter - Professional popup interface with smooth animations - Better contrast and readability across all themes (default, playground, chalkboard, galaxy, nature) - Improved accessibility with proper focus management - Enhanced mobile responsiveness with touch-optimized controls Files Modified: - Menu system: tablet-menu.js, tablet-menu.css - Theme engine: styles.css, themes integration - Admin interface: index.html, ui.js - Input systems: game.js, unified-write.js, handwriting.js - Authentication: auth.js with tablet menu cleanup
593 lines
21 KiB
HTML
593 lines
21 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Native Windows Handwriting Test</title>
|
|
<style>
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.test-container {
|
|
background: white;
|
|
padding: 30px;
|
|
border-radius: 20px;
|
|
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.status-bar {
|
|
padding: 15px;
|
|
border-radius: 10px;
|
|
margin: 15px 0;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.status-success {
|
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
|
color: white;
|
|
}
|
|
|
|
.status-warning {
|
|
background: linear-gradient(135deg, #ffeaa7 0%, #fab1a0 100%);
|
|
color: #2d3436;
|
|
}
|
|
|
|
.status-error {
|
|
background: linear-gradient(135deg, #fd79a8 0%, #e84393 100%);
|
|
color: white;
|
|
}
|
|
|
|
.canvas-container {
|
|
border: 3px dashed #e0e6ed;
|
|
border-radius: 15px;
|
|
padding: 20px;
|
|
text-align: center;
|
|
margin: 20px 0;
|
|
background: #f8f9fa;
|
|
}
|
|
|
|
#testCanvas {
|
|
border: 2px solid #dee2e6;
|
|
border-radius: 10px;
|
|
background: white;
|
|
cursor: crosshair;
|
|
touch-action: none;
|
|
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.button-group {
|
|
display: flex;
|
|
gap: 10px;
|
|
justify-content: center;
|
|
margin: 20px 0;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.btn {
|
|
padding: 12px 24px;
|
|
border: none;
|
|
border-radius: 25px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
}
|
|
|
|
.btn-success {
|
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
|
color: white;
|
|
}
|
|
|
|
.btn-warning {
|
|
background: linear-gradient(135deg, #ffeaa7 0%, #fab1a0 100%);
|
|
color: #2d3436;
|
|
}
|
|
|
|
.btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
|
|
}
|
|
|
|
.result-box {
|
|
background: #f8f9fa;
|
|
border: 2px solid #dee2e6;
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
margin: 15px 0;
|
|
min-height: 60px;
|
|
}
|
|
|
|
.result-text {
|
|
font-size: 24px;
|
|
font-weight: bold;
|
|
color: #2c3e50;
|
|
text-align: center;
|
|
}
|
|
|
|
.api-info {
|
|
background: #e9ecef;
|
|
padding: 15px;
|
|
border-radius: 10px;
|
|
margin: 10px 0;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.feature-test {
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
margin: 10px 0;
|
|
}
|
|
|
|
.feature-test h4 {
|
|
margin: 0 0 10px 0;
|
|
color: #495057;
|
|
}
|
|
|
|
.test-results {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
gap: 15px;
|
|
margin: 20px 0;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="test-container">
|
|
<h1>🖊️ Windows Native Handwriting Recognition Test</h1>
|
|
<p>This page tests various Windows handwriting recognition APIs available in Edge and Windows.</p>
|
|
|
|
<div id="overallStatus" class="status-bar status-warning">
|
|
🔍 Checking Windows handwriting support...
|
|
</div>
|
|
|
|
<div class="test-results">
|
|
<div class="feature-test">
|
|
<h4>🌐 Web Handwriting API</h4>
|
|
<div id="webApiStatus">Testing...</div>
|
|
<div id="webApiDetails" class="api-info"></div>
|
|
</div>
|
|
|
|
<div class="feature-test">
|
|
<h4>🖊️ Windows Ink Platform</h4>
|
|
<div id="inkApiStatus">Testing...</div>
|
|
<div id="inkApiDetails" class="api-info"></div>
|
|
</div>
|
|
|
|
<div class="feature-test">
|
|
<h4>👆 Pointer Events API</h4>
|
|
<div id="pointerApiStatus">Testing...</div>
|
|
<div id="pointerApiDetails" class="api-info"></div>
|
|
</div>
|
|
|
|
<div class="feature-test">
|
|
<h4>📱 Touch Events API</h4>
|
|
<div id="touchApiStatus">Testing...</div>
|
|
<div id="touchApiDetails" class="api-info"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="canvas-container">
|
|
<h3>✍️ Test Canvas</h3>
|
|
<p>Draw or write something below to test recognition:</p>
|
|
<canvas id="testCanvas" width="400" height="200"></canvas>
|
|
</div>
|
|
|
|
<div class="button-group">
|
|
<button class="btn btn-success" onclick="recognizeHandwriting()">🔍 Recognize</button>
|
|
<button class="btn btn-warning" onclick="clearCanvas()">🧹 Clear</button>
|
|
<button class="btn btn-primary" onclick="testPressure()">💪 Test Pressure</button>
|
|
</div>
|
|
|
|
<div class="result-box">
|
|
<div class="result-text" id="recognitionResult">Write something and click Recognize!</div>
|
|
</div>
|
|
|
|
<div id="debugInfo" class="api-info" style="max-height: 200px; overflow-y: auto;">
|
|
<strong>Debug Log:</strong><br>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Global variables
|
|
let canvas, ctx;
|
|
let isDrawing = false;
|
|
let strokes = [];
|
|
let currentStroke = [];
|
|
let recognizer = null;
|
|
|
|
// Initialize when page loads
|
|
window.addEventListener('DOMContentLoaded', function() {
|
|
initializeCanvas();
|
|
testAllAPIs();
|
|
});
|
|
|
|
function log(message) {
|
|
const debugInfo = document.getElementById('debugInfo');
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
debugInfo.innerHTML += `[${timestamp}] ${message}<br>`;
|
|
debugInfo.scrollTop = debugInfo.scrollHeight;
|
|
console.log(message);
|
|
}
|
|
|
|
function updateStatus(elementId, status, message, details = '') {
|
|
const element = document.getElementById(elementId);
|
|
const detailsElement = document.getElementById(elementId.replace('Status', 'Details'));
|
|
|
|
element.textContent = message;
|
|
element.className = status;
|
|
|
|
if (detailsElement && details) {
|
|
detailsElement.textContent = details;
|
|
}
|
|
}
|
|
|
|
function initializeCanvas() {
|
|
canvas = document.getElementById('testCanvas');
|
|
ctx = canvas.getContext('2d');
|
|
|
|
ctx.lineCap = 'round';
|
|
ctx.lineJoin = 'round';
|
|
ctx.lineWidth = 3;
|
|
ctx.strokeStyle = '#2c3e50';
|
|
|
|
// Add event listeners
|
|
setupCanvasEvents();
|
|
log('✅ Canvas initialized');
|
|
}
|
|
|
|
function setupCanvasEvents() {
|
|
// Pointer events (best for stylus)
|
|
if ('PointerEvent' in window) {
|
|
canvas.addEventListener('pointerdown', startDrawing);
|
|
canvas.addEventListener('pointermove', draw);
|
|
canvas.addEventListener('pointerup', stopDrawing);
|
|
log('📱 Pointer events registered');
|
|
}
|
|
|
|
// Mouse events (fallback)
|
|
canvas.addEventListener('mousedown', startDrawing);
|
|
canvas.addEventListener('mousemove', draw);
|
|
canvas.addEventListener('mouseup', stopDrawing);
|
|
|
|
// Touch events
|
|
canvas.addEventListener('touchstart', (e) => {
|
|
e.preventDefault();
|
|
startDrawing(e.touches[0]);
|
|
});
|
|
canvas.addEventListener('touchmove', (e) => {
|
|
e.preventDefault();
|
|
draw(e.touches[0]);
|
|
});
|
|
canvas.addEventListener('touchend', (e) => {
|
|
e.preventDefault();
|
|
stopDrawing();
|
|
});
|
|
}
|
|
|
|
function startDrawing(e) {
|
|
isDrawing = true;
|
|
currentStroke = [];
|
|
|
|
const point = getPoint(e);
|
|
currentStroke.push(point);
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(point.x, point.y);
|
|
|
|
log(`🖊️ Started stroke at (${point.x.toFixed(1)}, ${point.y.toFixed(1)})`);
|
|
}
|
|
|
|
function draw(e) {
|
|
if (!isDrawing) return;
|
|
|
|
const point = getPoint(e);
|
|
currentStroke.push(point);
|
|
|
|
ctx.lineTo(point.x, point.y);
|
|
ctx.stroke();
|
|
}
|
|
|
|
function stopDrawing() {
|
|
if (!isDrawing) return;
|
|
isDrawing = false;
|
|
|
|
if (currentStroke.length > 0) {
|
|
strokes.push([...currentStroke]);
|
|
log(`✅ Stroke completed: ${currentStroke.length} points`);
|
|
}
|
|
currentStroke = [];
|
|
}
|
|
|
|
function getPoint(e) {
|
|
const rect = canvas.getBoundingClientRect();
|
|
const point = {
|
|
x: e.clientX - rect.left,
|
|
y: e.clientY - rect.top,
|
|
timestamp: Date.now()
|
|
};
|
|
|
|
// Add extra data if available
|
|
if (e.pressure !== undefined) {
|
|
point.pressure = e.pressure;
|
|
}
|
|
if (e.tiltX !== undefined) {
|
|
point.tiltX = e.tiltX;
|
|
}
|
|
if (e.tiltY !== undefined) {
|
|
point.tiltY = e.tiltY;
|
|
}
|
|
if (e.force !== undefined) {
|
|
point.force = e.force;
|
|
}
|
|
|
|
return point;
|
|
}
|
|
|
|
async function testAllAPIs() {
|
|
log('🔍 Starting comprehensive API testing...');
|
|
|
|
// Test Web Handwriting API
|
|
await testWebHandwritingAPI();
|
|
|
|
// Test Windows Ink API
|
|
testWindowsInkAPI();
|
|
|
|
// Test Pointer Events
|
|
testPointerEventsAPI();
|
|
|
|
// Test Touch Events
|
|
testTouchEventsAPI();
|
|
|
|
// Update overall status
|
|
updateOverallStatus();
|
|
}
|
|
|
|
async function testWebHandwritingAPI() {
|
|
try {
|
|
if ('createHandwritingRecognizer' in navigator) {
|
|
log('🌐 Web Handwriting API detected!');
|
|
|
|
const query = {
|
|
languages: ['en'],
|
|
alternatives: 3,
|
|
inputType: 'text'
|
|
};
|
|
|
|
const queryResult = await navigator.queryHandwritingRecognizer(query);
|
|
|
|
if (queryResult) {
|
|
recognizer = await navigator.createHandwritingRecognizer(query);
|
|
updateStatus('webApiStatus', 'status-success', '✅ Available and Ready');
|
|
updateStatus('webApiDetails', '', '', 'Web Handwriting API successfully initialized');
|
|
log('✅ Web Handwriting recognizer created successfully');
|
|
return true;
|
|
} else {
|
|
updateStatus('webApiStatus', 'status-warning', '⚠️ Query Failed');
|
|
updateStatus('webApiDetails', '', '', 'API exists but query returned false');
|
|
log('⚠️ Web Handwriting API query returned false');
|
|
return false;
|
|
}
|
|
} else {
|
|
updateStatus('webApiStatus', 'status-error', '❌ Not Available');
|
|
updateStatus('webApiDetails', '', '', 'navigator.createHandwritingRecognizer not found');
|
|
log('❌ Web Handwriting API not available');
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
updateStatus('webApiStatus', 'status-error', '❌ Error');
|
|
updateStatus('webApiDetails', '', '', `Error: ${error.message}`);
|
|
log(`❌ Web Handwriting API error: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function testWindowsInkAPI() {
|
|
try {
|
|
if (typeof Windows !== 'undefined' && Windows.UI && Windows.UI.Input && Windows.UI.Input.Inking) {
|
|
updateStatus('inkApiStatus', 'status-success', '✅ Available');
|
|
updateStatus('inkApiDetails', '', '', 'Windows.UI.Input.Inking namespace detected');
|
|
log('✅ Windows Ink API available');
|
|
return true;
|
|
} else {
|
|
updateStatus('inkApiStatus', 'status-warning', '⚠️ Not Available');
|
|
updateStatus('inkApiDetails', '', '', 'Windows Runtime APIs not accessible (requires UWP context)');
|
|
log('⚠️ Windows Ink API not available (not in UWP context)');
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
updateStatus('inkApiStatus', 'status-error', '❌ Error');
|
|
updateStatus('inkApiDetails', '', '', `Error: ${error.message}`);
|
|
log(`❌ Windows Ink API error: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function testPointerEventsAPI() {
|
|
if ('PointerEvent' in window) {
|
|
updateStatus('pointerApiStatus', 'status-success', '✅ Available');
|
|
updateStatus('pointerApiDetails', '', '', 'Pointer Events Level 2 support detected');
|
|
log('✅ Pointer Events API available');
|
|
return true;
|
|
} else {
|
|
updateStatus('pointerApiStatus', 'status-error', '❌ Not Available');
|
|
updateStatus('pointerApiDetails', '', '', 'PointerEvent constructor not found');
|
|
log('❌ Pointer Events API not available');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function testTouchEventsAPI() {
|
|
if ('TouchEvent' in window || 'ontouchstart' in window) {
|
|
updateStatus('touchApiStatus', 'status-success', '✅ Available');
|
|
updateStatus('touchApiDetails', '', '', 'Touch Events support detected');
|
|
log('✅ Touch Events API available');
|
|
return true;
|
|
} else {
|
|
updateStatus('touchApiStatus', 'status-warning', '⚠️ Not Available');
|
|
updateStatus('touchApiDetails', '', '', 'No touch event support (desktop only?)');
|
|
log('⚠️ Touch Events API not available');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function updateOverallStatus() {
|
|
const overallStatus = document.getElementById('overallStatus');
|
|
|
|
if (recognizer) {
|
|
overallStatus.className = 'status-bar status-success';
|
|
overallStatus.textContent = '✅ Native handwriting recognition is available!';
|
|
log('🎉 Native handwriting recognition ready for use');
|
|
} else {
|
|
overallStatus.className = 'status-bar status-warning';
|
|
overallStatus.textContent = '⚠️ Native handwriting not available, using enhanced fallback';
|
|
log('⚠️ Falling back to enhanced pattern recognition');
|
|
}
|
|
}
|
|
|
|
async function recognizeHandwriting() {
|
|
const resultDiv = document.getElementById('recognitionResult');
|
|
|
|
if (strokes.length === 0) {
|
|
resultDiv.textContent = 'Please draw something first!';
|
|
log('⚠️ No strokes to recognize');
|
|
return;
|
|
}
|
|
|
|
resultDiv.textContent = 'Recognizing...';
|
|
log(`🔍 Starting recognition of ${strokes.length} strokes`);
|
|
|
|
try {
|
|
// Try Web Handwriting API first
|
|
if (recognizer && recognizer.startDrawing) {
|
|
const result = await recognizeWithWebAPI();
|
|
if (result) {
|
|
resultDiv.textContent = `"${result}"`;
|
|
log(`✅ Web API recognition: "${result}"`);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Fallback to simple pattern recognition
|
|
const fallbackResult = recognizeWithPatterns();
|
|
resultDiv.textContent = fallbackResult ? `"${fallbackResult}"` : 'Could not recognize';
|
|
log(`🔄 Fallback recognition: "${fallbackResult || 'failed'}"`);
|
|
|
|
} catch (error) {
|
|
resultDiv.textContent = `Error: ${error.message}`;
|
|
log(`❌ Recognition error: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
async function recognizeWithWebAPI() {
|
|
try {
|
|
const drawing = recognizer.startDrawing();
|
|
|
|
for (const stroke of strokes) {
|
|
const apiStroke = drawing.addStroke();
|
|
for (const point of stroke) {
|
|
apiStroke.addPoint({
|
|
x: point.x,
|
|
y: point.y,
|
|
t: point.timestamp
|
|
});
|
|
}
|
|
}
|
|
|
|
const predictions = await recognizer.getPrediction();
|
|
|
|
if (predictions && predictions.length > 0) {
|
|
return predictions[0].text;
|
|
}
|
|
|
|
return null;
|
|
} catch (error) {
|
|
log(`❌ Web API recognition failed: ${error.message}`);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function recognizeWithPatterns() {
|
|
// Simple pattern recognition based on stroke count and shape
|
|
const strokeCount = strokes.length;
|
|
|
|
if (strokeCount === 1) {
|
|
return 'O'; // Single stroke likely a circle
|
|
} else if (strokeCount === 2) {
|
|
return 'H'; // Two strokes likely H or similar
|
|
} else if (strokeCount === 3) {
|
|
return 'Boy'; // Three strokes, guess Boy
|
|
} else if (strokeCount >= 4) {
|
|
return 'Love'; // Four+ strokes, guess Love
|
|
}
|
|
|
|
return 'Unknown';
|
|
}
|
|
|
|
function clearCanvas() {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
strokes = [];
|
|
currentStroke = [];
|
|
document.getElementById('recognitionResult').textContent = 'Write something and click Recognize!';
|
|
log('🧹 Canvas cleared');
|
|
}
|
|
|
|
function testPressure() {
|
|
log('💪 Testing pressure sensitivity...');
|
|
const resultDiv = document.getElementById('recognitionResult');
|
|
|
|
if (strokes.length === 0) {
|
|
resultDiv.textContent = 'Draw something to test pressure!';
|
|
return;
|
|
}
|
|
|
|
let pressureCount = 0;
|
|
let totalPressure = 0;
|
|
|
|
for (const stroke of strokes) {
|
|
for (const point of stroke) {
|
|
if (point.pressure !== undefined) {
|
|
pressureCount++;
|
|
totalPressure += point.pressure;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pressureCount > 0) {
|
|
const avgPressure = (totalPressure / pressureCount).toFixed(3);
|
|
resultDiv.textContent = `Pressure detected! Avg: ${avgPressure}`;
|
|
log(`✅ Pressure data: ${pressureCount} points, average ${avgPressure}`);
|
|
} else {
|
|
resultDiv.textContent = 'No pressure data detected';
|
|
log('⚠️ No pressure sensitivity detected');
|
|
}
|
|
}
|
|
|
|
// Enable experimental features message
|
|
window.addEventListener('load', function() {
|
|
setTimeout(() => {
|
|
if (!recognizer) {
|
|
log('💡 To enable Web Handwriting API in Edge:');
|
|
log(' 1. Go to edge://flags/');
|
|
log(' 2. Search for "Experimental Web Platform features"');
|
|
log(' 3. Enable it and restart Edge');
|
|
log(' 4. Or try Chrome Canary with --enable-experimental-web-platform-features');
|
|
}
|
|
}, 2000);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |