Spaces:
Running
Running
Add 1 files
Browse files- index.html +64 -166
index.html
CHANGED
|
@@ -3,6 +3,7 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
| 6 |
<title>ESP32/Arduino Code Uploader</title>
|
| 7 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.43.0/min/vs/loader.js"></script>
|
|
@@ -68,6 +69,12 @@
|
|
| 68 |
max-width: 500px;
|
| 69 |
width: 90%;
|
| 70 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
</style>
|
| 72 |
</head>
|
| 73 |
<body class="bg-gray-50 min-h-screen">
|
|
@@ -76,10 +83,13 @@
|
|
| 76 |
<div class="permission-content">
|
| 77 |
<h2 class="text-xl font-bold mb-4">Serial Port Access Required</h2>
|
| 78 |
<p class="mb-4">To connect to your device, you need to grant permission to access serial ports. Please click the button below and select your device from the browser's prompt.</p>
|
| 79 |
-
<div class="flex
|
| 80 |
-
<button id="requestPermissionBtn" class="bg-blue-600 hover:bg-blue-700 text-white py-2 px-6 rounded-md">
|
| 81 |
<i class="fas fa-key mr-2"></i> Grant Permission
|
| 82 |
</button>
|
|
|
|
|
|
|
|
|
|
| 83 |
</div>
|
| 84 |
</div>
|
| 85 |
</div>
|
|
@@ -93,6 +103,21 @@
|
|
| 93 |
<p class="text-gray-600 mt-2">Upload and manage your sketches for ESP32 and Arduino devices</p>
|
| 94 |
</header>
|
| 95 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
| 97 |
<!-- Left Column - Connection & Upload -->
|
| 98 |
<div class="lg:col-span-1 space-y-6">
|
|
@@ -143,7 +168,7 @@
|
|
| 143 |
</div>
|
| 144 |
|
| 145 |
<div class="pt-2">
|
| 146 |
-
<button id="connectBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md flex items-center justify-center">
|
| 147 |
<i class="fas fa-link mr-2"></i>
|
| 148 |
Connect
|
| 149 |
</button>
|
|
@@ -350,6 +375,9 @@
|
|
| 350 |
let activePorts = [];
|
| 351 |
|
| 352 |
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
|
|
|
|
|
|
| 353 |
// Tab switching
|
| 354 |
const editorTab = document.getElementById('editorTab');
|
| 355 |
const serialTab = document.getElementById('serialTab');
|
|
@@ -579,6 +607,21 @@
|
|
| 579 |
const portHelp = document.getElementById('portHelp');
|
| 580 |
const permissionModal = document.getElementById('permissionModal');
|
| 581 |
const requestPermissionBtn = document.getElementById('requestPermissionBtn');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 582 |
|
| 583 |
// Show permission modal
|
| 584 |
function showPermissionModal() {
|
|
@@ -590,6 +633,11 @@
|
|
| 590 |
permissionModal.style.display = 'none';
|
| 591 |
}
|
| 592 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 593 |
// Request permission button handler
|
| 594 |
requestPermissionBtn.addEventListener('click', async () => {
|
| 595 |
try {
|
|
@@ -609,7 +657,7 @@
|
|
| 609 |
} catch (error) {
|
| 610 |
if (error.name === 'NotFoundError') {
|
| 611 |
addToOutputConsole('No device was selected', 'info');
|
| 612 |
-
portHelp.textContent = 'No device selected';
|
| 613 |
} else if (error.name === 'SecurityError') {
|
| 614 |
addToOutputConsole('Permission denied for serial port access', 'error');
|
| 615 |
portHelp.textContent = 'Permission denied. Please allow serial port access.';
|
|
@@ -622,6 +670,8 @@
|
|
| 622 |
});
|
| 623 |
|
| 624 |
async function refreshPortList() {
|
|
|
|
|
|
|
| 625 |
portSelect.innerHTML = '<option value="">Select a port</option>';
|
| 626 |
activePorts = [];
|
| 627 |
|
|
@@ -665,12 +715,15 @@
|
|
| 665 |
portHelp.textContent = `Found ${ports.length} serial port(s)`;
|
| 666 |
addToOutputConsole(`Found ${ports.length} serial port(s)`);
|
| 667 |
} catch (error) {
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
|
|
|
|
|
|
|
|
|
|
| 674 |
}
|
| 675 |
}
|
| 676 |
|
|
@@ -804,160 +857,5 @@
|
|
| 804 |
connectBtn.classList.remove('bg-red-600', 'hover:bg-red-700');
|
| 805 |
connectBtn.classList.add('bg-blue-600', 'hover:bg-blue-700');
|
| 806 |
|
| 807 |
-
connectionStatus
|
| 808 |
-
connectionStatus.classList.add('bg-gray-100', 'text-gray-700');
|
| 809 |
-
connectionStatus.innerHTML = '<i class="fas fa-circle mr-2 text-gray-400"></i><span>Disconnected</span>';
|
| 810 |
-
|
| 811 |
-
addToOutputConsole('Disconnected from device');
|
| 812 |
-
uploadBtn.disabled = true;
|
| 813 |
-
}
|
| 814 |
-
|
| 815 |
-
async function readSerialData() {
|
| 816 |
-
while (isReading && port.readable) {
|
| 817 |
-
try {
|
| 818 |
-
const { value, done } = await reader.read();
|
| 819 |
-
if (done) {
|
| 820 |
-
reader.releaseLock();
|
| 821 |
-
break;
|
| 822 |
-
}
|
| 823 |
-
|
| 824 |
-
if (value) {
|
| 825 |
-
const text = new TextDecoder().decode(value);
|
| 826 |
-
addToSerialMonitor(text);
|
| 827 |
-
}
|
| 828 |
-
} catch (error) {
|
| 829 |
-
addToOutputConsole(`Error reading from serial port: ${error}`, 'error');
|
| 830 |
-
await disconnectFromPort();
|
| 831 |
-
break;
|
| 832 |
-
}
|
| 833 |
-
}
|
| 834 |
-
}
|
| 835 |
-
|
| 836 |
-
connectBtn.addEventListener('click', async () => {
|
| 837 |
-
if (isConnected) {
|
| 838 |
-
await disconnectFromPort();
|
| 839 |
-
} else {
|
| 840 |
-
await connectToPort();
|
| 841 |
-
}
|
| 842 |
-
});
|
| 843 |
-
|
| 844 |
-
// Upload simulation with board-specific settings
|
| 845 |
-
uploadBtn.addEventListener('click', async () => {
|
| 846 |
-
if (!isConnected) {
|
| 847 |
-
addToOutputConsole('Please connect to a device first', 'error');
|
| 848 |
-
return;
|
| 849 |
-
}
|
| 850 |
-
|
| 851 |
-
const boardType = boardSelect.value;
|
| 852 |
-
const config = boardConfigurations[boardType];
|
| 853 |
-
|
| 854 |
-
const uploadProgress = document.getElementById('uploadProgress');
|
| 855 |
-
const progressBar = document.getElementById('progressBar');
|
| 856 |
-
const progressPercent = document.getElementById('progressPercent');
|
| 857 |
-
|
| 858 |
-
uploadProgress.classList.remove('hidden');
|
| 859 |
-
progressBar.style.width = '0%';
|
| 860 |
-
progressPercent.textContent = '0%';
|
| 861 |
-
|
| 862 |
-
// Show board-specific settings in output
|
| 863 |
-
addToOutputConsole(`Preparing to upload to ${config.name}`);
|
| 864 |
-
addToOutputConsole(`Board: ${config.name}`);
|
| 865 |
-
|
| 866 |
-
if (boardType.startsWith('esp32') || boardType === 'cyd_esp32') {
|
| 867 |
-
addToOutputConsole(`Flash Mode: ${document.getElementById('flashMode')?.value || config.flashMode}`);
|
| 868 |
-
addToOutputConsole(`Flash Frequency: ${document.getElementById('flashFreq')?.value || config.flashFreq}`);
|
| 869 |
-
addToOutputConsole(`Upload Speed: ${document.getElementById('uploadSpeed')?.value || config.uploadSpeed}`);
|
| 870 |
-
} else {
|
| 871 |
-
// Arduino settings
|
| 872 |
-
addToOutputConsole(`Processor: ${config.processor}`);
|
| 873 |
-
addToOutputConsole(`Programmer: ${document.getElementById('programmer')?.value || config.programmer}`);
|
| 874 |
-
}
|
| 875 |
-
|
| 876 |
-
addToOutputConsole('Compiling sketch...');
|
| 877 |
-
|
| 878 |
-
// Simulate compilation and upload
|
| 879 |
-
let progress = 0;
|
| 880 |
-
const interval = setInterval(() => {
|
| 881 |
-
progress += Math.random() * 10;
|
| 882 |
-
if (progress > 100) progress = 100;
|
| 883 |
-
|
| 884 |
-
progressBar.style.width = `${progress}%`;
|
| 885 |
-
progressPercent.textContent = `${Math.floor(progress)}%`;
|
| 886 |
-
|
| 887 |
-
if (progress === 100) {
|
| 888 |
-
clearInterval(interval);
|
| 889 |
-
setTimeout(() => {
|
| 890 |
-
uploadProgress.classList.add('hidden');
|
| 891 |
-
addToOutputConsole('Upload complete!');
|
| 892 |
-
addToSerialMonitor('Sketch uploaded successfully');
|
| 893 |
-
|
| 894 |
-
// Board-specific success message
|
| 895 |
-
if (boardType === 'cyd_esp32') {
|
| 896 |
-
addToOutputConsole('CYD ESP32-2432S028: Touch screen initialized', 'success');
|
| 897 |
-
} else if (boardType === 'uno') {
|
| 898 |
-
addToOutputConsole('Arduino UNO: Sketch running', 'success');
|
| 899 |
-
}
|
| 900 |
-
}, 500);
|
| 901 |
-
}
|
| 902 |
-
}, 200);
|
| 903 |
-
});
|
| 904 |
-
|
| 905 |
-
// Verify button with board-specific settings
|
| 906 |
-
document.getElementById('verifyBtn').addEventListener('click', () => {
|
| 907 |
-
const boardType = boardSelect.value;
|
| 908 |
-
const config = boardConfigurations[boardType];
|
| 909 |
-
|
| 910 |
-
addToOutputConsole(`Verifying sketch for ${config.name}...`);
|
| 911 |
-
|
| 912 |
-
setTimeout(() => {
|
| 913 |
-
if (boardType.startsWith('esp32') || boardType === 'cyd_esp32') {
|
| 914 |
-
addToOutputConsole('Sketch uses ' + Math.floor(Math.random() * 20 + 10) + 'KB (' +
|
| 915 |
-
Math.floor(Math.random() * 10 + 5) + '%) of program storage space.');
|
| 916 |
-
addToOutputConsole('Global variables use ' + Math.floor(Math.random() * 5 + 1) + 'KB of dynamic memory.');
|
| 917 |
-
} else {
|
| 918 |
-
// Arduino boards
|
| 919 |
-
addToOutputConsole('Sketch uses ' + Math.floor(Math.random() * 30 + 5) + ' bytes (' +
|
| 920 |
-
Math.floor(Math.random() * 10 + 2) + '%) of program storage space.');
|
| 921 |
-
addToOutputConsole('Global variables use ' + Math.floor(Math.random() * 200 + 50) + ' bytes of dynamic memory.');
|
| 922 |
-
}
|
| 923 |
-
|
| 924 |
-
addToOutputConsole('Done verifying sketch.', 'success');
|
| 925 |
-
}, 1500);
|
| 926 |
-
});
|
| 927 |
-
|
| 928 |
-
// Output console helper
|
| 929 |
-
function addToOutputConsole(text, type = 'info') {
|
| 930 |
-
const console = document.getElementById('outputConsole');
|
| 931 |
-
const line = document.createElement('p');
|
| 932 |
-
|
| 933 |
-
if (type === 'error') {
|
| 934 |
-
line.className = 'text-red-400';
|
| 935 |
-
line.textContent = '! ' + text;
|
| 936 |
-
} else if (type === 'success') {
|
| 937 |
-
line.className = 'text-green-400';
|
| 938 |
-
line.textContent = '✓ ' + text;
|
| 939 |
-
} else {
|
| 940 |
-
line.textContent = '> ' + text;
|
| 941 |
-
}
|
| 942 |
-
|
| 943 |
-
console.appendChild(line);
|
| 944 |
-
console.scrollTop = console.scrollHeight;
|
| 945 |
-
}
|
| 946 |
-
|
| 947 |
-
// Check if Web Serial API is available
|
| 948 |
-
if (!('serial' in navigator)) {
|
| 949 |
-
addToOutputConsole('Web Serial API not supported in this browser. Try Chrome or Edge.', 'error');
|
| 950 |
-
portHelp.textContent = 'Web Serial API not supported. Use Chrome or Edge.';
|
| 951 |
-
refreshPorts.disabled = true;
|
| 952 |
-
connectBtn.disabled = true;
|
| 953 |
-
} else {
|
| 954 |
-
// Initial port refresh
|
| 955 |
-
refreshPortList();
|
| 956 |
-
}
|
| 957 |
-
|
| 958 |
-
// Trigger initial board selection
|
| 959 |
-
boardSelect.dispatchEvent(new Event('change'));
|
| 960 |
-
});
|
| 961 |
-
</script>
|
| 962 |
-
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=drbaker171/esp" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
|
| 963 |
</html>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<meta http-equiv="Permissions-Policy" content="serial=(self)">
|
| 7 |
<title>ESP32/Arduino Code Uploader</title>
|
| 8 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.43.0/min/vs/loader.js"></script>
|
|
|
|
| 69 |
max-width: 500px;
|
| 70 |
width: 90%;
|
| 71 |
}
|
| 72 |
+
.browser-warning {
|
| 73 |
+
background-color: #fef3c7;
|
| 74 |
+
border-left: 4px solid #f59e0b;
|
| 75 |
+
padding: 1rem;
|
| 76 |
+
margin-bottom: 1rem;
|
| 77 |
+
}
|
| 78 |
</style>
|
| 79 |
</head>
|
| 80 |
<body class="bg-gray-50 min-h-screen">
|
|
|
|
| 83 |
<div class="permission-content">
|
| 84 |
<h2 class="text-xl font-bold mb-4">Serial Port Access Required</h2>
|
| 85 |
<p class="mb-4">To connect to your device, you need to grant permission to access serial ports. Please click the button below and select your device from the browser's prompt.</p>
|
| 86 |
+
<div class="flex flex-col space-y-4">
|
| 87 |
+
<button id="requestPermissionBtn" class="bg-blue-600 hover:bg-blue-700 text-white py-2 px-6 rounded-md flex items-center justify-center">
|
| 88 |
<i class="fas fa-key mr-2"></i> Grant Permission
|
| 89 |
</button>
|
| 90 |
+
<button id="learnMoreBtn" class="text-blue-600 hover:text-blue-800 py-2 px-6 rounded-md flex items-center justify-center">
|
| 91 |
+
<i class="fas fa-info-circle mr-2"></i> Learn More About Serial Access
|
| 92 |
+
</button>
|
| 93 |
</div>
|
| 94 |
</div>
|
| 95 |
</div>
|
|
|
|
| 103 |
<p class="text-gray-600 mt-2">Upload and manage your sketches for ESP32 and Arduino devices</p>
|
| 104 |
</header>
|
| 105 |
|
| 106 |
+
<!-- Browser Warning (hidden by default) -->
|
| 107 |
+
<div id="browserWarning" class="browser-warning hidden">
|
| 108 |
+
<div class="flex items-start">
|
| 109 |
+
<div class="flex-shrink-0">
|
| 110 |
+
<i class="fas fa-exclamation-triangle text-yellow-600"></i>
|
| 111 |
+
</div>
|
| 112 |
+
<div class="ml-3">
|
| 113 |
+
<h3 class="text-sm font-medium text-yellow-800">Browser Compatibility Notice</h3>
|
| 114 |
+
<div class="mt-2 text-sm text-yellow-700">
|
| 115 |
+
<p>This application requires the Web Serial API which is currently only supported in Chrome/Edge 89+ and Opera 76+.</p>
|
| 116 |
+
</div>
|
| 117 |
+
</div>
|
| 118 |
+
</div>
|
| 119 |
+
</div>
|
| 120 |
+
|
| 121 |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
| 122 |
<!-- Left Column - Connection & Upload -->
|
| 123 |
<div class="lg:col-span-1 space-y-6">
|
|
|
|
| 168 |
</div>
|
| 169 |
|
| 170 |
<div class="pt-2">
|
| 171 |
+
<button id="connectBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed">
|
| 172 |
<i class="fas fa-link mr-2"></i>
|
| 173 |
Connect
|
| 174 |
</button>
|
|
|
|
| 375 |
let activePorts = [];
|
| 376 |
|
| 377 |
document.addEventListener('DOMContentLoaded', function() {
|
| 378 |
+
// Check browser compatibility first
|
| 379 |
+
checkBrowserCompatibility();
|
| 380 |
+
|
| 381 |
// Tab switching
|
| 382 |
const editorTab = document.getElementById('editorTab');
|
| 383 |
const serialTab = document.getElementById('serialTab');
|
|
|
|
| 607 |
const portHelp = document.getElementById('portHelp');
|
| 608 |
const permissionModal = document.getElementById('permissionModal');
|
| 609 |
const requestPermissionBtn = document.getElementById('requestPermissionBtn');
|
| 610 |
+
const learnMoreBtn = document.getElementById('learnMoreBtn');
|
| 611 |
+
const browserWarning = document.getElementById('browserWarning');
|
| 612 |
+
|
| 613 |
+
// Check browser compatibility
|
| 614 |
+
function checkBrowserCompatibility() {
|
| 615 |
+
if (!('serial' in navigator)) {
|
| 616 |
+
browserWarning.classList.remove('hidden');
|
| 617 |
+
refreshPorts.disabled = true;
|
| 618 |
+
connectBtn.disabled = true;
|
| 619 |
+
portHelp.textContent = 'Web Serial API not supported in this browser. Use Chrome/Edge 89+ or Opera 76+.';
|
| 620 |
+
addToOutputConsole('Web Serial API not supported in this browser. Try Chrome/Edge 89+ or Opera 76+.', 'error');
|
| 621 |
+
return false;
|
| 622 |
+
}
|
| 623 |
+
return true;
|
| 624 |
+
}
|
| 625 |
|
| 626 |
// Show permission modal
|
| 627 |
function showPermissionModal() {
|
|
|
|
| 633 |
permissionModal.style.display = 'none';
|
| 634 |
}
|
| 635 |
|
| 636 |
+
// Learn more button handler
|
| 637 |
+
learnMoreBtn.addEventListener('click', () => {
|
| 638 |
+
window.open('https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API', '_blank');
|
| 639 |
+
});
|
| 640 |
+
|
| 641 |
// Request permission button handler
|
| 642 |
requestPermissionBtn.addEventListener('click', async () => {
|
| 643 |
try {
|
|
|
|
| 657 |
} catch (error) {
|
| 658 |
if (error.name === 'NotFoundError') {
|
| 659 |
addToOutputConsole('No device was selected', 'info');
|
| 660 |
+
portHelp.textContent = 'No device selected. Make sure your device is connected.';
|
| 661 |
} else if (error.name === 'SecurityError') {
|
| 662 |
addToOutputConsole('Permission denied for serial port access', 'error');
|
| 663 |
portHelp.textContent = 'Permission denied. Please allow serial port access.';
|
|
|
|
| 670 |
});
|
| 671 |
|
| 672 |
async function refreshPortList() {
|
| 673 |
+
if (!checkBrowserCompatibility()) return;
|
| 674 |
+
|
| 675 |
portSelect.innerHTML = '<option value="">Select a port</option>';
|
| 676 |
activePorts = [];
|
| 677 |
|
|
|
|
| 715 |
portHelp.textContent = `Found ${ports.length} serial port(s)`;
|
| 716 |
addToOutputConsole(`Found ${ports.length} serial port(s)`);
|
| 717 |
} catch (error) {
|
| 718 |
+
if (error.name === 'SecurityError') {
|
| 719 |
+
addToOutputConsole('Permission denied for serial port access. Please grant permission.', 'error');
|
| 720 |
+
portHelp.textContent = 'Permission denied. Please allow serial port access.';
|
| 721 |
+
showPermissionModal();
|
| 722 |
+
} else {
|
| 723 |
+
addToOutputConsole(`Error accessing serial ports: ${error}`, 'error');
|
| 724 |
+
portSelect.innerHTML = '<option value="">Error accessing ports</option>';
|
| 725 |
+
portHelp.textContent = 'Error accessing ports. Please try again.';
|
| 726 |
+
}
|
| 727 |
}
|
| 728 |
}
|
| 729 |
|
|
|
|
| 857 |
connectBtn.classList.remove('bg-red-600', 'hover:bg-red-700');
|
| 858 |
connectBtn.classList.add('bg-blue-600', 'hover:bg-blue-700');
|
| 859 |
|
| 860 |
+
connectionStatus
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 861 |
</html>
|