job folders, selection
This commit is contained in:
+115
-7
@@ -164,6 +164,47 @@
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.button-with-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.button-with-checkbox label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.button-with-checkbox input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
padding: 10px 20px;
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: background 0.2s;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cancel-button:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.cancel-button.active {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -198,14 +239,30 @@
|
||||
{# County-specific commands #}
|
||||
<div class="button-grid">
|
||||
{% if 'lake' in script_config %}
|
||||
<button class="script-button lake" onclick="runScript('{{ script_name }}', 'lake')">
|
||||
{{ script_name|replace('-', ' ')|title }} (Lake)
|
||||
</button>
|
||||
<div class="button-with-checkbox">
|
||||
<button class="script-button lake" onclick="runScript('{{ script_name }}', 'lake')">
|
||||
{{ script_name|replace('-', ' ')|title }} (Lake)
|
||||
</button>
|
||||
{% if script_name == 'diff-addresses' %}
|
||||
<label>
|
||||
<input type="checkbox" id="force-redownload-lake">
|
||||
Force OSM Redownload
|
||||
</label>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if 'sumter' in script_config %}
|
||||
<button class="script-button sumter" onclick="runScript('{{ script_name }}', 'sumter')">
|
||||
{{ script_name|replace('-', ' ')|title }} (Sumter)
|
||||
</button>
|
||||
<div class="button-with-checkbox">
|
||||
<button class="script-button sumter" onclick="runScript('{{ script_name }}', 'sumter')">
|
||||
{{ script_name|replace('-', ' ')|title }} (Sumter)
|
||||
</button>
|
||||
{% if script_name == 'diff-addresses' %}
|
||||
<label>
|
||||
<input type="checkbox" id="force-redownload-sumter">
|
||||
Force OSM Redownload
|
||||
</label>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -243,6 +300,7 @@
|
||||
<div class="log-viewer">
|
||||
<h2>Script Output</h2>
|
||||
<div id="status" class="status-message"></div>
|
||||
<button id="cancelButton" class="cancel-button" onclick="cancelJob()">Cancel Job</button>
|
||||
<div id="logs" class="log-box"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -269,6 +327,19 @@
|
||||
btn.disabled = true;
|
||||
});
|
||||
|
||||
// Show cancel button
|
||||
document.getElementById('cancelButton').classList.add('active');
|
||||
|
||||
// Check if force redownload is checked (for diff-addresses)
|
||||
let forceDownload = false;
|
||||
if (scriptName === 'diff-addresses') {
|
||||
const checkboxId = `force-redownload-${county}`;
|
||||
const checkbox = document.getElementById(checkboxId);
|
||||
if (checkbox) {
|
||||
forceDownload = checkbox.checked;
|
||||
}
|
||||
}
|
||||
|
||||
fetch('/api/run-script', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -276,7 +347,8 @@
|
||||
},
|
||||
body: JSON.stringify({
|
||||
script: scriptName,
|
||||
county: county
|
||||
county: county,
|
||||
forceDownload: forceDownload
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
@@ -284,6 +356,7 @@
|
||||
if (data.error) {
|
||||
showStatus(`Error: ${data.error}`, 'error');
|
||||
enableButtons();
|
||||
document.getElementById('cancelButton').classList.remove('active');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -300,6 +373,40 @@
|
||||
.catch(error => {
|
||||
showStatus(`Error: ${error.message}`, 'error');
|
||||
enableButtons();
|
||||
document.getElementById('cancelButton').classList.remove('active');
|
||||
});
|
||||
}
|
||||
|
||||
function cancelJob() {
|
||||
if (!currentJobId) return;
|
||||
|
||||
if (!confirm('Are you sure you want to cancel this job?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/api/cancel-job', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
job_id: currentJobId
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showStatus('Job cancelled', 'info');
|
||||
clearInterval(logCheckInterval);
|
||||
enableButtons();
|
||||
document.getElementById('cancelButton').classList.remove('active');
|
||||
currentJobId = null;
|
||||
} else {
|
||||
showStatus(`Error cancelling job: ${data.error}`, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showStatus(`Error cancelling job: ${error.message}`, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -319,6 +426,7 @@
|
||||
clearInterval(logCheckInterval);
|
||||
showStatus('Script completed', 'success');
|
||||
enableButtons();
|
||||
document.getElementById('cancelButton').classList.remove('active');
|
||||
currentJobId = null;
|
||||
}
|
||||
})
|
||||
|
||||
+140
-1
@@ -149,12 +149,143 @@
|
||||
.load-button:hover {
|
||||
background: #218838 !important;
|
||||
}
|
||||
|
||||
.selection-box {
|
||||
position: absolute;
|
||||
border: 2px dashed #007bff;
|
||||
background: rgba(0, 123, 255, 0.1);
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.hamburger-button {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 1001;
|
||||
background: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.hamburger-button span {
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 3px;
|
||||
background: #333;
|
||||
border-radius: 2px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.hamburger-button.active span:nth-child(1) {
|
||||
transform: rotate(45deg) translate(6px, 6px);
|
||||
}
|
||||
|
||||
.hamburger-button.active span:nth-child(2) {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.hamburger-button.active span:nth-child(3) {
|
||||
transform: rotate(-45deg) translate(6px, -6px);
|
||||
}
|
||||
|
||||
.close-button {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
line-height: 1;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.close-button:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Mobile styles */
|
||||
@media (max-width: 768px) {
|
||||
.hamburger-button {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.controls {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: -100%;
|
||||
width: 80%;
|
||||
max-width: 320px;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
transition: right 0.3s ease;
|
||||
border-radius: 0;
|
||||
box-shadow: -2px 0 10px rgba(0,0,0,0.2);
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
.controls.open {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.controls h3 {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.controls label {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Overlay for mobile */
|
||||
.overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.overlay.active {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
|
||||
<div class="controls">
|
||||
<!-- Hamburger menu button for mobile -->
|
||||
<button class="hamburger-button" id="hamburgerButton" aria-label="Toggle menu">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
|
||||
<!-- Overlay for mobile -->
|
||||
<div class="overlay" id="overlay"></div>
|
||||
|
||||
<div class="controls" id="controls">
|
||||
<button class="close-button" id="closeButton" aria-label="Close menu">×</button>
|
||||
<h3>Layer Controls (top to bottom)</h3>
|
||||
<div id="layerList">
|
||||
<div class="layer-item" draggable="true" data-layer="diff">
|
||||
@@ -185,6 +316,14 @@
|
||||
Hide highway=service
|
||||
</label>
|
||||
|
||||
<h3 style="margin-top: 15px;">Selection Mode</h3>
|
||||
<button id="multiSelectToggle" style="width: 100%; padding: 8px; background: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px;">
|
||||
Multi-Select: OFF
|
||||
</button>
|
||||
<div style="margin-top: 8px; padding: 8px; background: #e7f3ff; border-radius: 4px; font-size: 11px; color: #004085;">
|
||||
<strong>Tip:</strong> Enable multi-select or hold Shift to select multiple features by clicking or dragging a box
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top: 15px;">Load Data</h3>
|
||||
<div class="file-input-group">
|
||||
<label for="countySelect">County:</label>
|
||||
|
||||
Reference in New Issue
Block a user