fix: mobile menu styling + nextcloud search/mtime/bambu hint
Deploy via Portainer / deploy (push) Successful in 1s
Deploy via Portainer / deploy (push) Successful in 1s
This commit is contained in:
@@ -1970,7 +1970,14 @@ def _parse_propfind(xml_bytes, base_url, username, current_path):
|
||||
items.append({'name': name, 'path': rel, 'is_dir': is_dir,
|
||||
'size': size, 'mtime': mtime, 'ext': ext if not is_dir else 'dir'})
|
||||
|
||||
items.sort(key=lambda x: (0 if x['is_dir'] else 1, x['name'].lower()))
|
||||
def _mtime_ts(m):
|
||||
try:
|
||||
from email.utils import parsedate_to_datetime
|
||||
return parsedate_to_datetime(m).timestamp()
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
items.sort(key=lambda x: (0 if x['is_dir'] else 1, -_mtime_ts(x['mtime'])))
|
||||
return items
|
||||
|
||||
@app.route('/api/nextcloud/browse')
|
||||
|
||||
@@ -106,6 +106,34 @@
|
||||
.stock-low { color: #f59e0b; }
|
||||
.stock-empty { color: #ef4444; }
|
||||
|
||||
/* ── Mobile offcanvas nav — mêmes styles que #sidebar nav ──────────── */
|
||||
#mobileSidebar nav { flex: 1; padding: .5rem 0; }
|
||||
#mobileSidebar nav a {
|
||||
display: flex; align-items: center; gap: .65rem;
|
||||
padding: .6rem 1.25rem; color: #9ba3af;
|
||||
text-decoration: none; font-size: .875rem;
|
||||
transition: background .15s, color .15s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#mobileSidebar nav a:hover,
|
||||
#mobileSidebar nav a.active { background: #2e323d; color: #fff; }
|
||||
#mobileSidebar nav a.active { border-left: 3px solid #ff6b35; }
|
||||
#mobileSidebar nav a i { flex-shrink: 0; font-size: 1rem; width: 1.1rem; text-align: center; }
|
||||
#mobileSidebar nav .nav-label { opacity: 1 !important; pointer-events: auto !important; }
|
||||
#mobileSidebar nav .nav-section {
|
||||
padding: .75rem 1.25rem .25rem; font-size: .7rem;
|
||||
text-transform: uppercase; letter-spacing: .08em; color: #4b5563;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
#mobileSidebar .sidebar-user {
|
||||
padding: .75rem 1.25rem; border-top: 1px solid #2e323d;
|
||||
opacity: 1 !important; pointer-events: auto !important;
|
||||
}
|
||||
#mobileSidebar .sidebar-user div { font-size: .75rem; color: #9ba3af; }
|
||||
#mobileSidebar .sidebar-user a {
|
||||
font-size: .72rem; color: #6b7280; text-decoration: none; display: block; margin-top: .25rem;
|
||||
}
|
||||
|
||||
/* ── Mobile responsive ───────────────────────────────────────────────── */
|
||||
@media (max-width: 767.98px) {
|
||||
#sidebar { display: none !important; }
|
||||
|
||||
+53
-14
@@ -317,16 +317,22 @@
|
||||
<div class="modal fade" id="ncPickerModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="modal-header pb-2">
|
||||
<div class="flex-fill">
|
||||
<h5 class="modal-title mb-1"><i class="bi bi-cloud me-2"></i>Nextcloud — Choisir un fichier</h5>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0 small" id="ncBreadcrumb"></ol>
|
||||
<ol class="breadcrumb mb-2 small" id="ncBreadcrumb"></ol>
|
||||
</nav>
|
||||
<input type="text" id="ncSearchInput" class="form-control form-control-sm"
|
||||
placeholder="Filtrer par nom…" autocomplete="off">
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
<button type="button" class="btn-close ms-2 align-self-start mt-1" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body p-0" id="ncPickerBody" style="max-height:60vh;overflow-y:auto">
|
||||
<div id="ncBambuHint" class="alert alert-info d-none m-2 mb-0 py-2 px-3 small" role="alert">
|
||||
<i class="bi bi-info-circle me-1"></i>
|
||||
Sélectionnez le fichier <code>.gcode.3mf</code> pour importer poids, durée et données de découpe.
|
||||
</div>
|
||||
<div class="modal-body p-0" id="ncPickerBody" style="max-height:55vh;overflow-y:auto">
|
||||
<div class="text-center text-muted py-5">
|
||||
<i class="bi bi-cloud fs-1 d-block mb-2"></i>
|
||||
Cliquez sur le bouton <i class="bi bi-cloud-arrow-down"></i> pour ouvrir.
|
||||
@@ -752,16 +758,20 @@ if (document.getElementById('weight_g').value) recalc();
|
||||
// ── Nextcloud File Picker ─────────────────────────────────────────────────────
|
||||
let ncPickerMode = 'source'; // 'source' = remplit seulement sourceFile, 'bambu' = remplit + parse 3MF
|
||||
(function () {
|
||||
const modal = document.getElementById('ncPickerModal');
|
||||
const body = document.getElementById('ncPickerBody');
|
||||
const bread = document.getElementById('ncBreadcrumb');
|
||||
const sfInput = document.getElementById('sourceFile');
|
||||
const modal = document.getElementById('ncPickerModal');
|
||||
const body = document.getElementById('ncPickerBody');
|
||||
const bread = document.getElementById('ncBreadcrumb');
|
||||
const sfInput = document.getElementById('sourceFile');
|
||||
const searchInput = document.getElementById('ncSearchInput');
|
||||
const bambuHint = document.getElementById('ncBambuHint');
|
||||
|
||||
if (!modal) return;
|
||||
|
||||
let currentPath = '';
|
||||
let allItems = [];
|
||||
|
||||
async function browse(path) {
|
||||
searchInput.value = '';
|
||||
body.innerHTML = '<div class="text-center py-4"><div class="spinner-border spinner-border-sm"></div> Chargement…</div>';
|
||||
try {
|
||||
const r = await fetch('/api/nextcloud/browse?path=' + encodeURIComponent(path));
|
||||
@@ -771,13 +781,21 @@ let ncPickerMode = 'source'; // 'source' = remplit seulement sourceFile, 'bambu'
|
||||
return;
|
||||
}
|
||||
currentPath = d.path;
|
||||
allItems = d.items;
|
||||
renderBreadcrumb(d.breadcrumb);
|
||||
renderItems(d.items);
|
||||
renderItems(allItems);
|
||||
} catch (e) {
|
||||
body.innerHTML = `<div class="alert alert-danger m-3">${e.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
searchInput.addEventListener('input', () => {
|
||||
const q = searchInput.value.trim().toLowerCase();
|
||||
if (!q) { renderItems(allItems); return; }
|
||||
const filtered = allItems.filter(it => it.name.toLowerCase().includes(q));
|
||||
renderItems(filtered);
|
||||
});
|
||||
|
||||
function renderBreadcrumb(crumbs) {
|
||||
bread.innerHTML = crumbs.map((c, i) =>
|
||||
i < crumbs.length - 1
|
||||
@@ -798,9 +816,22 @@ let ncPickerMode = 'source'; // 'source' = remplit seulement sourceFile, 'bambu'
|
||||
'dir': 'bi-folder-fill text-warning',
|
||||
};
|
||||
|
||||
function fmtMtime(mtime) {
|
||||
if (!mtime) return '';
|
||||
try {
|
||||
const d = new Date(mtime);
|
||||
const now = new Date();
|
||||
const diffMs = now - d;
|
||||
if (diffMs < 86400000) return 'Aujourd\'hui';
|
||||
if (diffMs < 172800000) return 'Hier';
|
||||
if (diffMs < 604800000) return Math.floor(diffMs/86400000) + 'j';
|
||||
return d.toLocaleDateString('fr-FR', {day:'2-digit', month:'2-digit', year:'2-digit'});
|
||||
} catch (e) { return ''; }
|
||||
}
|
||||
|
||||
function renderItems(items) {
|
||||
if (!items.length) {
|
||||
body.innerHTML = '<div class="text-center text-muted py-4">Dossier vide.</div>';
|
||||
body.innerHTML = '<div class="text-center text-muted py-4">Aucun fichier trouvé.</div>';
|
||||
return;
|
||||
}
|
||||
body.innerHTML = '';
|
||||
@@ -808,12 +839,16 @@ let ncPickerMode = 'source'; // 'source' = remplit seulement sourceFile, 'bambu'
|
||||
list.className = 'list-group list-group-flush';
|
||||
items.forEach(item => {
|
||||
const icon = EXT_ICONS[item.ext] || 'bi-file text-muted';
|
||||
const size = item.is_dir ? '' : ` <small class="text-muted">(${(item.size/1024/1024).toFixed(1)} Mo)</small>`;
|
||||
const meta = item.is_dir ? '' :
|
||||
`<small class="text-muted ms-auto text-nowrap" style="font-size:.75rem">
|
||||
${fmtMtime(item.mtime)}${item.size ? ' · ' + (item.size/1024/1024).toFixed(1) + ' Mo' : ''}
|
||||
</small>`;
|
||||
const el = document.createElement('button');
|
||||
el.type = 'button';
|
||||
el.className = 'list-group-item list-group-item-action d-flex align-items-center gap-2 py-2';
|
||||
el.innerHTML = `<i class="bi ${icon} fs-5" style="flex-shrink:0"></i>
|
||||
<span class="flex-fill text-start">${item.name}${size}</span>
|
||||
<span class="flex-fill text-start">${item.name}</span>
|
||||
${meta}
|
||||
${item.is_dir ? '<i class="bi bi-chevron-right text-muted"></i>' : '<i class="bi bi-check2-circle text-success"></i>'}`;
|
||||
if (item.is_dir) {
|
||||
el.addEventListener('click', () => browse(item.path));
|
||||
@@ -869,8 +904,12 @@ let ncPickerMode = 'source'; // 'source' = remplit seulement sourceFile, 'bambu'
|
||||
ncPickerMode = 'source'; // reset
|
||||
}
|
||||
|
||||
// Ouvrir = charger le dossier racine
|
||||
modal.addEventListener('show.bs.modal', () => { if (!currentPath) browse(''); });
|
||||
// Ouvrir = charger le dossier racine + afficher/masquer hint bambu
|
||||
modal.addEventListener('show.bs.modal', () => {
|
||||
bambuHint.classList.toggle('d-none', ncPickerMode !== 'bambu');
|
||||
searchInput.value = '';
|
||||
if (!currentPath) browse('');
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user