Existing ASP.NET API with vanilla JS SPA, WindowWatcher, Chrome extension, and MCP server. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
120 lines
5.4 KiB
JavaScript
120 lines
5.4 KiB
JavaScript
import * as api from '../api.js';
|
|
import { showModal } from '../components/modal.js';
|
|
|
|
const el = () => document.getElementById('page-mappings');
|
|
|
|
export async function initMappings() {
|
|
el().innerHTML = `
|
|
<h1 class="page-title">App Mappings</h1>
|
|
<div class="flex-between mb-16">
|
|
<div class="text-muted text-sm">Map process names, window titles, or URLs to categories</div>
|
|
<button class="btn btn-primary" id="btn-new-mapping">+ New Mapping</button>
|
|
</div>
|
|
<div id="mapping-list" class="card table-wrap"></div>`;
|
|
|
|
document.getElementById('btn-new-mapping').addEventListener('click', () => showMappingForm());
|
|
await loadMappings();
|
|
}
|
|
|
|
async function loadMappings() {
|
|
try {
|
|
const mappings = await api.mappings.list();
|
|
const container = document.getElementById('mapping-list');
|
|
if (!mappings || !mappings.length) {
|
|
container.innerHTML = `<div class="empty-state">No mappings configured</div>`;
|
|
return;
|
|
}
|
|
container.innerHTML = `
|
|
<table>
|
|
<thead><tr><th>Pattern</th><th>Match Type</th><th>Category</th><th>Friendly Name</th><th>Actions</th></tr></thead>
|
|
<tbody>
|
|
${mappings.map(m => `
|
|
<tr>
|
|
<td><code>${esc(m.pattern)}</code></td>
|
|
<td><span class="badge badge-pending">${m.matchType}</span></td>
|
|
<td>${esc(m.category)}</td>
|
|
<td>${esc(m.friendlyName) || '<span class="text-muted">-</span>'}</td>
|
|
<td>
|
|
<div class="btn-group">
|
|
<button class="btn btn-sm" data-edit="${m.id}">Edit</button>
|
|
<button class="btn btn-sm btn-danger" data-delete="${m.id}">Delete</button>
|
|
</div>
|
|
</td>
|
|
</tr>`).join('')}
|
|
</tbody>
|
|
</table>`;
|
|
container.querySelectorAll('[data-edit]').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const m = mappings.find(x => x.id === parseInt(btn.dataset.edit));
|
|
if (m) showMappingForm(m);
|
|
});
|
|
});
|
|
container.querySelectorAll('[data-delete]').forEach(btn => {
|
|
btn.addEventListener('click', () => confirmDelete(parseInt(btn.dataset.delete)));
|
|
});
|
|
} catch (e) {
|
|
document.getElementById('mapping-list').innerHTML = `<div class="empty-state">Failed to load mappings</div>`;
|
|
}
|
|
}
|
|
|
|
function showMappingForm(existing = null) {
|
|
const title = existing ? 'Edit Mapping' : 'New Mapping';
|
|
showModal(title, `
|
|
<div class="form-group">
|
|
<label class="form-label">Pattern *</label>
|
|
<input type="text" class="form-input" id="map-pattern" value="${esc(existing?.pattern || '')}">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Match Type *</label>
|
|
<select class="form-select" id="map-match-type">
|
|
<option value="ProcessName" ${existing?.matchType === 'ProcessName' ? 'selected' : ''}>Process Name</option>
|
|
<option value="TitleContains" ${existing?.matchType === 'TitleContains' ? 'selected' : ''}>Title Contains</option>
|
|
<option value="UrlContains" ${existing?.matchType === 'UrlContains' ? 'selected' : ''}>URL Contains</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Category *</label>
|
|
<input type="text" class="form-input" id="map-category" value="${esc(existing?.category || '')}">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Friendly Name</label>
|
|
<input type="text" class="form-input" id="map-friendly" value="${esc(existing?.friendlyName || '')}">
|
|
</div>`,
|
|
[
|
|
{ label: 'Cancel', onClick: () => {} },
|
|
{
|
|
label: existing ? 'Save' : 'Create', cls: 'btn-primary', onClick: async (modal) => {
|
|
const pattern = modal.querySelector('#map-pattern').value.trim();
|
|
const matchType = modal.querySelector('#map-match-type').value;
|
|
const category = modal.querySelector('#map-category').value.trim();
|
|
const friendlyName = modal.querySelector('#map-friendly').value.trim() || null;
|
|
if (!pattern || !category) { alert('Pattern and Category are required'); throw new Error('cancel'); }
|
|
const body = { pattern, matchType, category, friendlyName };
|
|
if (existing) await api.mappings.update(existing.id, body);
|
|
else await api.mappings.create(body);
|
|
loadMappings();
|
|
},
|
|
},
|
|
]);
|
|
setTimeout(() => document.getElementById('map-pattern')?.focus(), 100);
|
|
}
|
|
|
|
function confirmDelete(id) {
|
|
showModal('Delete Mapping', `<p>Are you sure you want to delete this mapping?</p>`, [
|
|
{ label: 'Cancel', onClick: () => {} },
|
|
{
|
|
label: 'Delete', cls: 'btn-danger', onClick: async () => {
|
|
await api.mappings.remove(id);
|
|
loadMappings();
|
|
},
|
|
},
|
|
]);
|
|
}
|
|
|
|
function esc(str) {
|
|
if (!str) return '';
|
|
const d = document.createElement('div');
|
|
d.textContent = str;
|
|
return d.innerHTML;
|
|
}
|