Upload files to "General"
This commit is contained in:
@@ -0,0 +1,397 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>ARRL Welfare Radiogram (Packet Radio)</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace; background: #f4f6f8; color: #222; padding: 20px; margin: 0; }
|
||||
.container { max-width: 880px; margin: 0 auto; background: #fff; padding: 24px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
|
||||
.header { background: #0b5394; color: white; padding: 12px; border-radius: 6px; text-align: center; margin-bottom: 18px; }
|
||||
.header h2 { margin: 0; font-size: 1.3rem; letter-spacing: 1px; }
|
||||
.header p { margin: 4px 0 0 0; font-size: 0.85rem; opacity: 0.9; }
|
||||
fieldset { border: 1px solid #ccc; border-radius: 6px; padding: 14px; margin-bottom: 16px; }
|
||||
legend { font-weight: bold; padding: 0 6px; font-size: 0.9em; color: #0b5394; }
|
||||
.legend-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
||||
.row { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 10px; }
|
||||
.col { flex: 1; min-width: 130px; display: flex; flex-direction: column; }
|
||||
.label-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; }
|
||||
label { font-size: 0.8rem; font-weight: 600; color: #444; }
|
||||
input[type="text"] { padding: 7px 8px; font-size: 0.9rem; border: 1px solid #aaa; border-radius: 4px; text-transform: uppercase; font-family: inherit; width: 100%; }
|
||||
.grid-5 { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; margin-bottom: 8px; }
|
||||
.word-box { display: flex; align-items: center; gap: 4px; }
|
||||
.word-box span { font-size: 0.75rem; font-weight: bold; width: 22px; text-align: right; color: #666; }
|
||||
.word-box input { width: 100%; padding: 6px 4px; text-align: center; }
|
||||
.actions { display: flex; gap: 12px; margin: 20px 0; }
|
||||
button { padding: 10px 14px; font-size: 0.95rem; font-weight: bold; border: none; border-radius: 6px; cursor: pointer; }
|
||||
.btn-now { background: #4a5568; color: #fff; padding: 4px 10px; font-size: 0.75rem; font-weight: normal; border-radius: 4px; }
|
||||
.btn-now:hover { background: #2d3748; }
|
||||
.btn-generate { background: #0b5394; color: #fff; flex: 1; }
|
||||
.btn-generate:hover { background: #083c6c; }
|
||||
.btn-copy { background: #2e7d32; color: #fff; flex: 1; }
|
||||
.btn-copy:hover { background: #1b5e20; }
|
||||
.btn-clear { background: #d32f2f; color: #fff; flex: 0.4; }
|
||||
.btn-clear:hover { background: #b71c1c; }
|
||||
pre { background: #1e1e1e; color: #00ff66; padding: 16px; border-radius: 6px; overflow-x: auto; font-family: "Courier New", Courier, monospace; font-size: 0.85rem; line-height: 1.35; white-space: pre; min-height: 150px; }
|
||||
.nav-bar { margin-bottom: 12px; font-size: 0.85rem; }
|
||||
.nav-bar a { color: #0b5394; text-decoration: none; font-weight: bold; }
|
||||
|
||||
/* Modal Save Dialog Window */
|
||||
.save-modal-backdrop {
|
||||
display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: rgba(0,0,0,0.55); z-index: 99999; justify-content: center; align-items: center;
|
||||
}
|
||||
.save-modal-backdrop.active { display: flex; }
|
||||
.save-modal-window {
|
||||
background: #ffffff; padding: 24px 28px; border-radius: 10px; width: 92%; max-width: 480px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.3); border: 1px solid #cbd5e1; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
.save-modal-window h3 { margin: 0 0 8px 0; font-size: 1.25rem; color: #1e293b; font-weight: bold; }
|
||||
.save-modal-window p { margin: 0 0 16px 0; font-size: 0.88rem; color: #64748b; line-height: 1.4; }
|
||||
.save-modal-window input[type="text"] {
|
||||
width: 100%; padding: 10px 12px; font-size: 0.95rem; border: 2px solid #94a3b8; border-radius: 6px;
|
||||
margin-bottom: 20px; font-family: inherit; box-sizing: border-box; text-transform: none;
|
||||
}
|
||||
.save-modal-window input[type="text"]:focus { border-color: #2563eb; outline: none; }
|
||||
.save-modal-actions { display: flex; gap: 10px; justify-content: flex-end; }
|
||||
.save-modal-btn { padding: 9px 18px; font-size: 0.9rem; font-weight: bold; border-radius: 6px; border: none; cursor: pointer; }
|
||||
.save-modal-btn-confirm { background: #2563eb; color: #ffffff; }
|
||||
.save-modal-btn-confirm:hover { background: #1d4ed8; }
|
||||
.save-modal-btn-cancel { background: #e2e8f0; color: #334155; }
|
||||
.save-modal-btn-cancel:hover { background: #cbd5e1; }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="nav-bar"><a href="../index.html">← Back to Packet Forms Hub</a></div>
|
||||
<div class="header">
|
||||
<h2>HEALTH & WELFARE RADIOGRAM</h2>
|
||||
<p>ARRL / NTS Standard Format for Packet Radio</p>
|
||||
</div>
|
||||
|
||||
<form id="radiogramForm" onsubmit="event.preventDefault(); return false;">
|
||||
<fieldset>
|
||||
<legend>PREAMBLE</legend>
|
||||
<div class="legend-bar">
|
||||
<span style="font-size: 0.85rem; color: #666; font-weight: bold;">Message Header</span>
|
||||
<button type="button" class="btn-now" id="btnRadioNow">Set Current UTC</button>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col" style="max-width: 100px;"><label for="msgNum">Number</label><input type="text" id="msgNum" placeholder="101"></div>
|
||||
<div class="col" style="max-width: 90px;"><label for="prec">Precedence</label><input type="text" id="prec" value="W" readonly style="background:#eee;"></div>
|
||||
<div class="col" style="max-width: 90px;"><label for="hx">HX</label><input type="text" id="hx" placeholder="HXC"></div>
|
||||
<div class="col"><label for="originStation">Station of Origin</label><input type="text" id="originStation" placeholder="CALLSIGN"></div>
|
||||
<div class="col" style="max-width: 90px;"><label for="check">Check</label><input type="text" id="check" readonly style="background:#eee;" placeholder="AUTO"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col"><label for="placeOrigin">Place of Origin</label><input type="text" id="placeOrigin" placeholder="CITY, STATE"></div>
|
||||
<div class="col"><label for="timeFiled">Time Filed</label><input type="text" id="timeFiled" placeholder="1430Z"></div>
|
||||
<div class="col"><label for="dateFiled">Date</label><input type="text" id="dateFiled" placeholder="SEP 04"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>TO (Recipient Info)</legend>
|
||||
<div class="row">
|
||||
<div class="col"><label for="toName">Name</label><input type="text" id="toName"></div>
|
||||
<div class="col"><label for="toPhone">Phone</label><input type="text" id="toPhone"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col"><label for="toAddress">Address</label><input type="text" id="toAddress"></div>
|
||||
<div class="col"><label for="toEmail">Email</label><input type="text" id="toEmail"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col" style="flex: 2;"><label for="toCity">City</label><input type="text" id="toCity"></div>
|
||||
<div class="col"><label for="toState">State</label><input type="text" id="toState"></div>
|
||||
<div class="col"><label for="toZip">Zip</label><input type="text" id="toZip"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>TEXT (5 Words Per Line, Max 25 Words - No Standard Punctuation)</legend>
|
||||
<div id="wordGrid"></div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>SIGNATURE & HANDLING</legend>
|
||||
<div class="row">
|
||||
<div class="col"><label for="sigName">Signature (Name / Callsign)</label><input type="text" id="sigName"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col"><label for="rcvdFrom">Rec'd From</label><input type="text" id="rcvdFrom"></div>
|
||||
<div class="col"><label for="rcvdTime">Time</label><input type="text" id="rcvdTime"></div>
|
||||
<div class="col"><label for="rcvdDate">Date</label><input type="text" id="rcvdDate"></div>
|
||||
<div class="col"><label for="sentTo">Sent To</label><input type="text" id="sentTo"></div>
|
||||
<div class="col"><label for="sentTime">Time</label><input type="text" id="sentTime"></div>
|
||||
<div class="col"><label for="sentDate">Date</label><input type="text" id="sentDate"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" class="btn-generate" id="btnGen">Generate ASCII</button>
|
||||
<button type="button" class="btn-save" id="btnSaveHtml" style="background:#5c6bc0; color:#fff; flex:1;">Save as HTML</button>
|
||||
<button type="button" class="btn-copy" id="btnCopy">Copy Text</button>
|
||||
<button type="button" class="btn-clear" id="btnClear">Clear</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<label style="font-weight:bold;">Formatted ASCII Output (Ready for Packet / BBS):</label>
|
||||
<pre id="outputArea">Form output will appear here...</pre>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Custom File Name Modal Window -->
|
||||
<div id="saveModalBackdrop" class="save-modal-backdrop">
|
||||
<div class="save-modal-window">
|
||||
<h3>Save Form as HTML</h3>
|
||||
<p>Enter a name for the saved offline HTML file:</p>
|
||||
<input type="text" id="saveModalFileName" spellcheck="false">
|
||||
<div class="save-modal-actions">
|
||||
<button type="button" class="save-modal-btn save-modal-btn-cancel" id="saveModalCancelBtn">Cancel</button>
|
||||
<button type="button" class="save-modal-btn save-modal-btn-confirm" id="saveModalSaveBtn">Save File</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const gridContainer = document.getElementById('wordGrid');
|
||||
for (let r = 0; r < 5; r++) {
|
||||
const rowDiv = document.createElement('div');
|
||||
rowDiv.className = 'grid-5';
|
||||
for (let c = 1; c <= 5; c++) {
|
||||
const idx = r * 5 + c;
|
||||
const box = document.createElement('div');
|
||||
box.className = 'word-box';
|
||||
box.innerHTML = '<span>' + idx + '.</span><input type="text" id="w' + idx + '" oninput="cleanWord(this)">';
|
||||
rowDiv.appendChild(box);
|
||||
}
|
||||
gridContainer.appendChild(rowDiv);
|
||||
}
|
||||
|
||||
function setCurrentDateTime() {
|
||||
const now = new Date();
|
||||
const hours = String(now.getUTCHours()).padStart(2, '0');
|
||||
const minutes = String(now.getUTCMinutes()).padStart(2, '0');
|
||||
document.getElementById('timeFiled').value = hours + minutes + 'Z';
|
||||
|
||||
const months = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
|
||||
const month = months[now.getUTCMonth()];
|
||||
const day = String(now.getUTCDate()).padStart(2, '0');
|
||||
document.getElementById('dateFiled').value = month + ' ' + day;
|
||||
}
|
||||
|
||||
function cleanWord(el) {
|
||||
el.value = el.value.replace(/[^a-zA-Z0-9\/]/g, '').toUpperCase();
|
||||
updateCheckCount();
|
||||
}
|
||||
|
||||
function updateCheckCount() {
|
||||
let count = 0;
|
||||
for (let i = 1; i <= 25; i++) {
|
||||
const el = document.getElementById('w' + i);
|
||||
if (el && el.value.trim() !== '') count++;
|
||||
}
|
||||
document.getElementById('check').value = count > 0 ? count : '';
|
||||
}
|
||||
|
||||
function pad(str, len) {
|
||||
str = (str || '').toString().toUpperCase();
|
||||
return str + ' '.repeat(Math.max(0, len - str.length));
|
||||
}
|
||||
|
||||
function generateOutput() {
|
||||
try {
|
||||
const num = pad(document.getElementById('msgNum').value, 6);
|
||||
const prec = "W";
|
||||
const hx = pad(document.getElementById('hx').value, 6);
|
||||
const origin = pad(document.getElementById('originStation').value, 9);
|
||||
const check = pad(document.getElementById('check').value, 6);
|
||||
const place = pad(document.getElementById('placeOrigin').value, 22);
|
||||
const time = pad(document.getElementById('timeFiled').value, 9);
|
||||
const date = pad(document.getElementById('dateFiled').value, 17);
|
||||
|
||||
const name = pad(document.getElementById('toName').value, 38);
|
||||
const phone = pad(document.getElementById('toPhone').value, 34);
|
||||
const addr = pad(document.getElementById('toAddress').value, 38);
|
||||
const email = pad(document.getElementById('toEmail').value, 34);
|
||||
const city = pad(document.getElementById('toCity').value, 38);
|
||||
const st = pad(document.getElementById('toState').value, 11);
|
||||
const zip = pad(document.getElementById('toZip').value, 15);
|
||||
|
||||
const sig = pad(document.getElementById('sigName').value, 84);
|
||||
const rFrom = pad(document.getElementById('rcvdFrom').value, 14);
|
||||
const rTime = pad(document.getElementById('rcvdTime').value, 9);
|
||||
const rDate = pad(document.getElementById('rcvdDate').value, 8);
|
||||
const sTo = pad(document.getElementById('sentTo').value, 14);
|
||||
const sTime = pad(document.getElementById('sentTime').value, 9);
|
||||
const sDate = pad(document.getElementById('sentDate').value, 8);
|
||||
|
||||
const divLine = "_".repeat(100);
|
||||
|
||||
let textLines = [];
|
||||
for (let r = 0; r < 5; r++) {
|
||||
let rowParts = [];
|
||||
for (let c = 1; c <= 5; c++) {
|
||||
const idx = r * 5 + c;
|
||||
const val = pad(document.getElementById('w' + idx).value, 17);
|
||||
const prefix = idx < 10 ? idx + '. ' : idx + '.';
|
||||
rowParts.push(prefix + ' ' + val);
|
||||
}
|
||||
textLines.push(rowParts.join(' '));
|
||||
}
|
||||
|
||||
const output =
|
||||
divLine + '\n' +
|
||||
'PREAMBLE: \n\n' +
|
||||
'| Number: ' + num + ' | Precedence: ' + prec + ' | HX: ' + hx + ' | Station of Origin: ' + origin + ' | Check: ' + check + ' | \n' +
|
||||
'| Place of Origin: ' + place + ' | Time Filed: ' + time + ' | Date: ' + date + ' |\n' +
|
||||
divLine + '\n' +
|
||||
'TO (Recipient Info):\n' +
|
||||
'Name: ' + name + ' Phone: ' + phone + '\n' +
|
||||
'Address: ' + addr + ' Email: ' + email + '\n' +
|
||||
'City: ' + city + ' State: ' + st + ' Zip: ' + zip + '\n' +
|
||||
divLine + '\n' +
|
||||
'TEXT (Write exactly 5 words per line, max 25 words. All uppercase, no standard punctuation):\n\n' +
|
||||
textLines[0] + '\n\n' +
|
||||
textLines[1] + '\n\n' +
|
||||
textLines[2] + '\n\n' +
|
||||
textLines[3] + '\n\n' +
|
||||
textLines[4] + '\n' +
|
||||
divLine + '\n' +
|
||||
'SIGNATURE (Sender Info):\n' +
|
||||
'Name/Callsign: ' + sig + '\n' +
|
||||
'Received From: ' + rFrom + ' Time: ' + rTime + ' Date: ' + rDate + ' | Sent To: ' + sTo + ' Time: ' + sTime + ' Date: ' + sDate + '\n' +
|
||||
divLine;
|
||||
|
||||
document.getElementById('outputArea').textContent = output;
|
||||
} catch(err) {
|
||||
alert("Error generating output: " + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
function copyOutput() {
|
||||
const text = document.getElementById('outputArea').textContent;
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
alert("Radiogram copied to clipboard!");
|
||||
}).catch(function() {
|
||||
fallbackCopy();
|
||||
});
|
||||
} else {
|
||||
fallbackCopy();
|
||||
}
|
||||
}
|
||||
|
||||
function fallbackCopy() {
|
||||
const pre = document.getElementById('outputArea');
|
||||
const range = document.createRange();
|
||||
range.selectNode(pre);
|
||||
window.getSelection().removeAllRanges();
|
||||
window.getSelection().addRange(range);
|
||||
document.execCommand('copy');
|
||||
alert("Copied to clipboard!");
|
||||
}
|
||||
|
||||
function clearForm() {
|
||||
if (confirm("Clear all form fields?")) {
|
||||
document.getElementById('radiogramForm').reset();
|
||||
document.getElementById('prec').value = 'W';
|
||||
document.getElementById('outputArea').textContent = 'Form output will appear here...';
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('btnRadioNow').addEventListener('click', setCurrentDateTime);
|
||||
document.getElementById('btnGen').addEventListener('click', generateOutput);
|
||||
document.getElementById('btnCopy').addEventListener('click', copyOutput);
|
||||
document.getElementById('btnClear').addEventListener('click', clearForm);
|
||||
|
||||
// --- File Name Modal Window & HTML Exporter ---
|
||||
function openSaveModal() {
|
||||
var now = new Date();
|
||||
var dStr = now.toISOString().slice(0, 10);
|
||||
var defName = 'welfare_radiogram_' + dStr + '.html';
|
||||
var modal = document.getElementById('saveModalBackdrop');
|
||||
var input = document.getElementById('saveModalFileName');
|
||||
if (input) input.value = defName;
|
||||
if (modal) modal.classList.add('active');
|
||||
setTimeout(function() {
|
||||
if (input) { input.focus(); input.select(); }
|
||||
}, 50);
|
||||
}
|
||||
|
||||
function closeSaveModal() {
|
||||
var modal = document.getElementById('saveModalBackdrop');
|
||||
if (modal) modal.classList.remove('active');
|
||||
}
|
||||
|
||||
function executeSaveHTML() {
|
||||
var input = document.getElementById('saveModalFileName');
|
||||
var chosenName = input ? input.value.trim() : '';
|
||||
if (!chosenName) return;
|
||||
if (!chosenName.toLowerCase().endsWith('.html') && !chosenName.toLowerCase().endsWith('.htm')) {
|
||||
chosenName += '.html';
|
||||
}
|
||||
closeSaveModal();
|
||||
|
||||
document.querySelectorAll('input[type="text"]').forEach(function(el) { el.setAttribute('value', el.value); });
|
||||
document.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(function(el) {
|
||||
if (el.checked) el.setAttribute('checked', 'checked');
|
||||
else el.removeAttribute('checked');
|
||||
});
|
||||
document.querySelectorAll('textarea').forEach(function(el) { el.textContent = el.value; });
|
||||
document.querySelectorAll('select').forEach(function(el) {
|
||||
Array.from(el.options).forEach(function(opt) {
|
||||
if (opt.value === el.value) opt.setAttribute('selected', 'selected');
|
||||
else opt.removeAttribute('selected');
|
||||
});
|
||||
});
|
||||
|
||||
var pre = document.getElementById('ascii-report') || document.getElementById('outputPre') || document.getElementById('preview') || document.getElementById('outputArea');
|
||||
if (pre) pre.textContent = pre.textContent;
|
||||
|
||||
var clonedDoc = document.documentElement.cloneNode(true);
|
||||
var modalInClone = clonedDoc.querySelector('#saveModalBackdrop');
|
||||
if (modalInClone) modalInClone.classList.remove('active');
|
||||
|
||||
var htmlContent = '<!DOCTYPE html>\n' + clonedDoc.outerHTML;
|
||||
var blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8' });
|
||||
var url = URL.createObjectURL(blob);
|
||||
var a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = chosenName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// Initialize event listeners safely after DOM loads
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
var cancelBtn = document.getElementById('saveModalCancelBtn');
|
||||
var confirmBtn = document.getElementById('saveModalSaveBtn');
|
||||
var modalInput = document.getElementById('saveModalFileName');
|
||||
var modalBackdrop = document.getElementById('saveModalBackdrop');
|
||||
|
||||
if (cancelBtn) cancelBtn.addEventListener('click', closeSaveModal);
|
||||
if (confirmBtn) confirmBtn.addEventListener('click', executeSaveHTML);
|
||||
if (modalInput) {
|
||||
modalInput.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter') { e.preventDefault(); executeSaveHTML(); }
|
||||
if (e.key === 'Escape') { e.preventDefault(); closeSaveModal(); }
|
||||
});
|
||||
}
|
||||
if (modalBackdrop) {
|
||||
modalBackdrop.addEventListener('click', function(e) {
|
||||
if (e.target === modalBackdrop) closeSaveModal();
|
||||
});
|
||||
}
|
||||
|
||||
var btnSaveMain = document.getElementById('btnSaveHtml');
|
||||
if (btnSaveMain) {
|
||||
btnSaveMain.addEventListener('click', openSaveModal);
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user