Add contacts download, improve channel naming / TS preselection

This commit is contained in:
Marcus Kida 2026-02-18 15:30:13 +01:00
parent f5eb913162
commit 3881780dbe
9 changed files with 203 additions and 13 deletions

View file

@ -67,6 +67,8 @@
var cpsRepeaterTags = document.getElementById("cps-repeater-tags");
var cpsAddTgInput = document.getElementById("cps-add-tg-input");
var cpsAcList = document.getElementById("cps-ac-list");
var cpsAliasFormat = document.getElementById("cps-alias-format");
var cpsContactsBtn = document.getElementById("cps-contacts-btn");
var cpsDownloadBtn = document.getElementById("cps-download-btn");
var cpsCopyBtn = document.getElementById("cps-copy-btn");
var cpsChannelCount = document.getElementById("cps-channel-count");
@ -325,7 +327,7 @@
var cc = r.color_code;
talkgroups.forEach(function (tg) {
var slot = tg.slot === "1" ? "SLOT1" : "SLOT2";
var alias = (r.callsign + " TG" + tg.id).substring(0, 16);
var alias = tg.name.substring(0, 16);
channels += buildChannel(alias, slot, cc, txHz, rxHz);
});
});
@ -397,6 +399,8 @@
function updateCpsButtons() {
var hasData = getActiveRepeaters().length > 0 && cpsTalkgroups.length > 0;
var hasTgs = cpsTalkgroups.length > 0;
cpsContactsBtn.disabled = !hasTgs;
cpsDownloadBtn.disabled = !hasData;
cpsCopyBtn.disabled = !hasData;
}
@ -408,6 +412,15 @@
cpsAcList.innerHTML = "";
}
function setSlot(idx, slot) {
cpsTalkgroups[idx].slot = slot;
if (cpsAliasFormat.value === "tg-ts") {
cpsTalkgroups[idx].name = formatTgAlias(cpsTalkgroups[idx].id, slot);
}
renderTgTable();
updateChannelCount();
}
function renderTgTable() {
cpsTgTbody.innerHTML = "";
cpsTalkgroups.forEach(function (tg, idx) {
@ -420,7 +433,19 @@
var tdName = document.createElement("td");
tdName.className = "tg-name";
tdName.contentEditable = "true";
tdName.spellcheck = false;
tdName.textContent = tg.name || t("cps_unknown_tg");
tdName.addEventListener("blur", (function (i) {
return function () {
var val = tdName.textContent.trim().substring(0, 16);
cpsTalkgroups[i].name = val;
tdName.textContent = val || t("cps_unknown_tg");
};
})(idx));
tdName.addEventListener("keydown", function (e) {
if (e.key === "Enter") { e.preventDefault(); tdName.blur(); }
});
tr.appendChild(tdName);
var tdSlot = document.createElement("td");
@ -435,10 +460,10 @@
btn2.textContent = "TS2";
if (tg.slot === "2") btn2.className = "active";
btn1.addEventListener("click", (function (i) {
return function () { cpsTalkgroups[i].slot = "1"; renderTgTable(); updateChannelCount(); };
return function () { setSlot(i, "1"); };
})(idx));
btn2.addEventListener("click", (function (i) {
return function () { cpsTalkgroups[i].slot = "2"; renderTgTable(); updateChannelCount(); };
return function () { setSlot(i, "2"); };
})(idx));
toggle.appendChild(btn1);
toggle.appendChild(btn2);
@ -470,15 +495,74 @@
});
}
function formatTgAlias(tgId, slot) {
var fmt = cpsAliasFormat.value;
if (fmt === "tg-ts") {
return ("TG" + tgId + "-TS" + slot).substring(0, 16);
}
return ("TG" + tgId + " " + (tgName(tgId) || "")).trim().substring(0, 16);
}
function reformatAllAliases() {
cpsTalkgroups.forEach(function (tg) {
tg.name = formatTgAlias(tg.id, tg.slot);
});
renderTgTable();
}
function defaultSlot(tgId) {
var first = String(tgId).charAt(0);
return (first === "8" || first === "9") ? "2" : "1";
}
function addTalkgroupById(tgId) {
if (cpsTalkgroups.some(function (tg) { return tg.id === tgId; })) return;
cpsTalkgroups.push({ id: tgId, name: tgName(tgId), slot: "2" });
var slot = defaultSlot(tgId);
cpsTalkgroups.push({ id: tgId, name: formatTgAlias(tgId, slot), slot: slot });
cpsTalkgroups.sort(function (a, b) { return a.id - b.id; });
renderTgTable();
updateChannelCount();
updateCpsButtons();
}
var CPS_CSV_HEADER = "ContactName,Delete_Contact,Rename_Contact,Comments," +
"Delete_FiveToneCalls,FiveToneCalls-S5CLDLL_5TTELEGRAM,FiveToneCalls-S5CLDLL_5TCALLADD," +
"Delete_MDCCalls,MDCCalls-AU_CALLLSTID,MDCCalls-AU_MDCSYS,MDCCalls-AU_RVRTPERS_Zone,MDCCalls-AU_RVRTPERS,MDCCalls-AU_SPTPLDPL,MDCCalls-AU_CALLTYPE," +
"Delete_QuikCallIICalls,QuikCallIICalls-QU_QCIISYS,QuikCallIICalls-QU_RVRTPERS_Zone,QuikCallIICalls-QU_RVRTPERS,QuikCallIICalls-QU_CALLFORMAT,QuikCallIICalls-QU_TONEATXFRE,QuikCallIICalls-QU_CODEA,QuikCallIICalls-QU_TONEBTXFRE,QuikCallIICalls-QU_CODEB,QuikCallIICalls-QU_STRIPPLDPL," +
"Delete_DigitalCalls,DigitalCalls-DU_CALLLSTID,DigitalCalls-DU_ROUTETYPE,DigitalCalls-DU_CALLPRCDTNEN,DigitalCalls-DU_RINGTYPE,DigitalCalls-DU_TXTMSGALTTNTP,DigitalCalls-DU_CALLTYPE,DigitalCalls-DU_OVCMCALL," +
"Delete_CapacityPlusCalls,CapacityPlusCalls-CAPPLUSUCL_CALLLSTID,CapacityPlusCalls-CAPPLUSUCL_ROUTETYPE,CapacityPlusCalls-CAPPLUSUCL_CALLPRCDTNEN,CapacityPlusCalls-CAPPLUSUCL_RINGTYPE,CapacityPlusCalls-CAPPLUSUCL_TXTMSGALTTNTP,CapacityPlusCalls-CAPPLUSUCL_CALLTYPE," +
"Delete_PhoneCalls,PhoneCalls-PHNUCLELL_CALLID,PhoneCalls-PHNUCLELL_RINGTYPE";
function generateContactsCsv(talkgroups) {
var rows = [CPS_CSV_HEADER];
talkgroups.forEach(function (tg) {
var name = ("TG" + tg.id + " " + (tg.name || "")).trim().substring(0, 16);
var row = name + ",False,,," +
"False,,," +
"False,,,,,," +
"False,,,,,,,,," +
"False," + tg.id + ",Regular,False,No Style,Repetitive,Group Call,False," +
"False,,Regular,False,No Style,Repetitive,Group Call," +
"False,,No Style";
rows.push(row);
});
return rows.join("\r\n");
}
function downloadContactsCsv() {
if (!cpsTalkgroups.length) return;
var csv = generateContactsCsv(cpsTalkgroups);
var blob = new Blob([csv], { type: "text/csv" });
var url = URL.createObjectURL(blob);
var a = document.createElement("a");
a.href = url;
a.download = "dmrmap-contacts.csv";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function downloadCpsXml() {
var active = getActiveRepeaters();
if (!cpsTalkgroups.length || !active.length) return;
@ -1277,6 +1361,8 @@
}
}
});
cpsAliasFormat.addEventListener("change", reformatAllAliases);
cpsContactsBtn.addEventListener("click", downloadContactsCsv);
cpsDownloadBtn.addEventListener("click", downloadCpsXml);
cpsCopyBtn.addEventListener("click", copyCpsXml);

View file

@ -87,6 +87,13 @@
<p class="cps-exp-note" data-i18n="cps_experimental_note">This feature is still in development. Please review the generated output before importing.</p>
<div class="cps-repeater-tags" id="cps-repeater-tags"></div>
<div id="cps-tg-section">
<div class="cps-alias-format">
<label data-i18n="cps_alias_format">Channel alias format</label>
<select id="cps-alias-format">
<option value="tg-name" selected>TG262 Deutschland</option>
<option value="tg-ts">TG262-TS2</option>
</select>
</div>
<table class="cps-tg-table">
<thead>
<tr>
@ -106,8 +113,19 @@
</div>
</div>
<div class="cps-modal-footer">
<button type="button" id="cps-download-btn" class="cps-btn-primary" disabled data-i18n="cps_download_xml">Download XML</button>
<button type="button" id="cps-copy-btn" class="cps-btn-secondary" disabled data-i18n="cps_copy_xml">Copy to Clipboard</button>
<div class="cps-export-step">
<span class="cps-step-label" data-i18n="cps_step1_label">Step 1: Import contacts</span>
<div class="cps-step-buttons">
<button type="button" id="cps-contacts-btn" class="cps-btn-primary" disabled data-i18n="cps_download_contacts">Download Contacts CSV</button>
</div>
</div>
<div class="cps-export-step">
<span class="cps-step-label" data-i18n="cps_step2_label">Step 2: Paste zone</span>
<div class="cps-step-buttons">
<button type="button" id="cps-download-btn" class="cps-btn-primary" disabled data-i18n="cps_download_xml">Download Zone XML</button>
<button type="button" id="cps-copy-btn" class="cps-btn-secondary" disabled data-i18n="cps_copy_xml">Copy to Clipboard</button>
</div>
</div>
</div>
</div>
</div>

View file

@ -46,7 +46,11 @@
"cps_tg_name": "Name",
"cps_tg_slot": "Zeitschlitz",
"cps_add_tg_placeholder": "TG per ID oder Name hinzufügen...",
"cps_download_xml": "XML herunterladen",
"cps_alias_format": "Kanal-Alias-Format",
"cps_step1_label": "Schritt 1: Kontakte importieren",
"cps_download_contacts": "Kontakte CSV herunterladen",
"cps_step2_label": "Schritt 2: Zone einfügen",
"cps_download_xml": "Zone XML herunterladen",
"cps_copy_xml": "In Zwischenablage",
"cps_copied": "Kopiert!",
"cps_unknown_tg": "Unbekannt",

View file

@ -46,7 +46,11 @@
"cps_tg_name": "Name",
"cps_tg_slot": "Timeslot",
"cps_add_tg_placeholder": "Add TG by ID or name...",
"cps_download_xml": "Download XML",
"cps_alias_format": "Channel alias format",
"cps_step1_label": "Step 1: Import contacts",
"cps_download_contacts": "Download Contacts CSV",
"cps_step2_label": "Step 2: Paste zone",
"cps_download_xml": "Download Zone XML",
"cps_copy_xml": "Copy to Clipboard",
"cps_copied": "Copied!",
"cps_unknown_tg": "Unknown",

View file

@ -46,7 +46,11 @@
"cps_tg_name": "Nombre",
"cps_tg_slot": "Timeslot",
"cps_add_tg_placeholder": "Añadir TG por ID o nombre...",
"cps_download_xml": "Descargar XML",
"cps_alias_format": "Formato de alias del canal",
"cps_step1_label": "Paso 1: Importar contactos",
"cps_download_contacts": "Descargar contactos CSV",
"cps_step2_label": "Paso 2: Pegar zona",
"cps_download_xml": "Descargar zona XML",
"cps_copy_xml": "Copiar al portapapeles",
"cps_copied": "¡Copiado!",
"cps_unknown_tg": "Desconocido",

View file

@ -46,7 +46,11 @@
"cps_tg_name": "Nom",
"cps_tg_slot": "Timeslot",
"cps_add_tg_placeholder": "Ajouter TG par ID ou nom...",
"cps_download_xml": "Télécharger XML",
"cps_alias_format": "Format d'alias du canal",
"cps_step1_label": "Étape 1 : Importer les contacts",
"cps_download_contacts": "Télécharger contacts CSV",
"cps_step2_label": "Étape 2 : Coller la zone",
"cps_download_xml": "Télécharger zone XML",
"cps_copy_xml": "Copier dans le presse-papiers",
"cps_copied": "Copié !",
"cps_unknown_tg": "Inconnu",

View file

@ -46,7 +46,11 @@
"cps_tg_name": "Nome",
"cps_tg_slot": "Timeslot",
"cps_add_tg_placeholder": "Aggiungi TG per ID o nome...",
"cps_download_xml": "Scarica XML",
"cps_alias_format": "Formato alias canale",
"cps_step1_label": "Passo 1: Importa contatti",
"cps_download_contacts": "Scarica contatti CSV",
"cps_step2_label": "Passo 2: Incolla zona",
"cps_download_xml": "Scarica zona XML",
"cps_copy_xml": "Copia negli appunti",
"cps_copied": "Copiato!",
"cps_unknown_tg": "Sconosciuto",

View file

@ -48,7 +48,11 @@
"cps_tg_name": "Nazwa",
"cps_tg_slot": "Timeslot",
"cps_add_tg_placeholder": "Dodaj TG po ID lub nazwie...",
"cps_download_xml": "Pobierz XML",
"cps_alias_format": "Format aliasu kanału",
"cps_step1_label": "Krok 1: Importuj kontakty",
"cps_download_contacts": "Pobierz kontakty CSV",
"cps_step2_label": "Krok 2: Wklej strefę",
"cps_download_xml": "Pobierz strefę XML",
"cps_copy_xml": "Kopiuj do schowka",
"cps_copied": "Skopiowano!",
"cps_unknown_tg": "Nieznany",

View file

@ -785,6 +785,48 @@ html, body {
opacity: 1;
}
.cps-alias-format {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.cps-alias-format label {
font-size: 11px;
font-weight: 600;
color: var(--text-muted);
white-space: nowrap;
}
.cps-alias-format select {
flex: 1;
padding: 4px 8px;
border: 1px solid var(--border);
border-radius: 4px;
font-size: 11px;
background: var(--input-bg);
color: var(--text);
outline: none;
}
.cps-tg-table .tg-name[contenteditable] {
cursor: text;
border-radius: 3px;
padding: 2px 4px;
margin: -2px -4px;
outline: none;
}
.cps-tg-table .tg-name[contenteditable]:hover {
background: var(--hover-bg);
}
.cps-tg-table .tg-name[contenteditable]:focus {
background: var(--input-bg);
box-shadow: 0 0 0 1px #4CAF50;
}
.cps-tg-table {
width: 100%;
border-collapse: collapse;
@ -902,11 +944,31 @@ html, body {
.cps-modal-footer {
display: flex;
gap: 8px;
flex-direction: column;
gap: 10px;
padding: 12px 18px;
border-top: 1px solid var(--border-light);
}
.cps-export-step {
display: flex;
flex-direction: column;
gap: 6px;
}
.cps-step-label {
font-size: 11px;
font-weight: 600;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.03em;
}
.cps-step-buttons {
display: flex;
gap: 8px;
}
.cps-btn-primary {
flex: 1;
padding: 8px 16px;