fix: opacité menus + contexte toutes vues + hover preview + split layout preview
- Menus dropdown et contextuel: fonds solides #1E1E1E au lieu de var(--bg) transparent - Menu contextuel: data-ws-id sur TOUS les éléments (tree/list/details/title/content) + onContextMenu simplifié (cherche [data-ws-id] uniquement, plus de fallback fragile) - Mouse-over preview (showPreview/hidePreview) ajouté sur toutes les vues - Preview panel: split layout flex à côté du contenu au lieu de position:fixed overlay ws-split > ws-main | preview-panel
This commit is contained in:
@@ -27,7 +27,7 @@
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
.ws-layout{max-width:800px;margin:0 auto;padding:40px 24px;}
|
||||
.ws-layout{max-width:800px;margin:0 auto;padding:40px 24px;}/* deprecated by ws-split */
|
||||
@media (max-width: 768px) { .ws-layout { padding: 20px 12px; } }
|
||||
.ws-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px;flex-wrap:wrap;gap:12px;}
|
||||
.ws-header h1{font-size:24px;}
|
||||
@@ -222,6 +222,12 @@
|
||||
.ws-table-compact td{padding:5px 10px;border-bottom:1px solid var(--border);}
|
||||
.ws-table-compact tr:hover td{background:var(--bg-hover);}
|
||||
|
||||
/* Split layout */
|
||||
.ws-split{display:flex;max-width:100%;margin:0 auto;padding:40px 24px 0;gap:0;min-height:calc(100vh - 44px);}
|
||||
.ws-main{flex:1;min-width:0;overflow:auto;padding-right:0;}
|
||||
.preview-panel + .ws-main{margin-right:0;}
|
||||
@media(max-width:768px){.ws-split{flex-direction:column;padding:20px 12px 0;}.preview-panel{width:100%;max-width:100%;min-width:100%;}.ws-main{width:100%;}}
|
||||
|
||||
/* Breadcrumb row */
|
||||
.breadcrumb-row{display:flex;align-items:center;gap:6px;padding:6px 8px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:10px 10px 0 0;margin-bottom:0;flex-wrap:wrap;}
|
||||
/* Toolbar row */
|
||||
@@ -263,13 +269,13 @@
|
||||
.view-dropdown-btn{display:flex;align-items:center;gap:6px;padding:5px 10px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:6px;cursor:pointer;font-size:12px;color:var(--text);white-space:nowrap;}
|
||||
.view-dropdown-btn:hover{background:var(--bg-hover);border-color:var(--accent);}
|
||||
.dropdown-arrow{font-size:10px;color:var(--text-tertiary);}
|
||||
.view-dropdown-menu{position:absolute;top:100%;left:0;margin-top:4px;background:var(--bg);border:1px solid var(--border-strong);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.4);z-index:250;min-width:140px;overflow:hidden;}
|
||||
.view-dropdown-menu{position:absolute;top:100%;left:0;margin-top:4px;background:#1E1E1E;border:1px solid var(--border-strong);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.6);z-index:250;min-width:140px;overflow:hidden;}
|
||||
.view-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 12px;background:none;border:none;color:var(--text);font-size:13px;cursor:pointer;text-align:left;}
|
||||
.view-menu-item:hover{background:var(--bg-hover);}
|
||||
.view-menu-item.active{background:rgba(35,131,226,.12);color:var(--accent);font-weight:500;}
|
||||
|
||||
/* Context menu */
|
||||
.ctx-menu{position:fixed;z-index:300;background:var(--bg);border:1px solid var(--border-strong);border-radius:10px;box-shadow:0 8px 30px rgba(0,0,0,.5);min-width:180px;overflow:hidden;padding:4px 0;}
|
||||
.ctx-menu{position:fixed;z-index:300;background:#1E1E1E;border:1px solid var(--border-strong);border-radius:10px;box-shadow:0 8px 30px rgba(0,0,0,.7);min-width:180px;overflow:hidden;padding:4px 0;}
|
||||
.ctx-item{display:flex;align-items:center;gap:8px;padding:7px 14px;font-size:13px;color:var(--text);cursor:pointer;transition:background 100ms;}
|
||||
.ctx-item:hover{background:var(--bg-hover);}
|
||||
.ctx-item.ctx-danger{color:var(--danger);}
|
||||
@@ -307,9 +313,8 @@
|
||||
/* Selected row highlight */
|
||||
.selected-row{background:rgba(35,131,226,.08)!important;}
|
||||
|
||||
/* Preview panel */
|
||||
.preview-panel{position:fixed;top:44px;right:0;width:380px;max-width:90vw;height:calc(100vh - 44px);background:var(--bg);border-left:1px solid var(--border-strong);box-shadow:-4px 0 20px rgba(0,0,0,.3);z-index:200;display:flex;flex-direction:column;overflow:hidden;}
|
||||
@media(max-width:768px){.preview-panel{width:100vw;max-width:100vw;}}
|
||||
/* Preview panel — side panel in split layout */
|
||||
.preview-panel{width:380px;min-width:300px;max-width:45vw;background:var(--bg-secondary);border-left:1px solid var(--border-strong);display:flex;flex-direction:column;overflow:hidden;flex-shrink:0;}
|
||||
.preview-panel-header{display:flex;align-items:center;gap:10px;padding:14px 16px;border-bottom:1px solid var(--border);flex-shrink:0;}
|
||||
.preview-panel-title{font-size:15px;font-weight:600;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
||||
.preview-close{background:none;border:none;color:var(--text-tertiary);font-size:20px;cursor:pointer;padding:2px 6px;border-radius:4px;line-height:1;}
|
||||
@@ -330,7 +335,7 @@
|
||||
.preview-leave-end{transform:translateX(100%);opacity:0;}
|
||||
</style>
|
||||
|
||||
<div class="ws-layout" x-data="wsInit()"
|
||||
<div class="ws-split" x-data="wsInit()"
|
||||
@dragover.prevent="onDragOver($event)"
|
||||
@dragleave="onDragLeave($event)"
|
||||
@drop.prevent="onDrop($event)"
|
||||
@@ -338,6 +343,7 @@
|
||||
@contextmenu.prevent="onContextMenu($event)"
|
||||
tabindex="0"
|
||||
x-ref="layout">
|
||||
<div class="ws-main">
|
||||
|
||||
<!-- Drop overlay -->
|
||||
<div class="drop-overlay" :class="{ active: isDragging }">
|
||||
@@ -465,6 +471,7 @@
|
||||
<li>
|
||||
<div class="ws-tree-item" :style="{paddingLeft:(node.depth||0)*20+'px'}"
|
||||
draggable="true"
|
||||
:data-ws-id="node.id"
|
||||
:data-folder-id="node.is_folder ? node.id : null"
|
||||
@dragstart="onInternalDragStart($event, node.id)"
|
||||
@dragend="draggedItemId = null; $el.style.opacity = ''"
|
||||
@@ -606,7 +613,7 @@
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<template x-for="node in displayTree" :key="'l'+node.id">
|
||||
<tr @click="node.is_folder ? navigateToFolder(node.id) : selectForPreview(node)" style="cursor:pointer">
|
||||
<tr :data-ws-id="node.id" @click="node.is_folder ? navigateToFolder(node.id) : selectForPreview(node)" @mouseenter="showPreview($event, node)" @mouseleave="hidePreview()" style="cursor:pointer">
|
||||
<td><span x-text="_fileIcon(node.name, node.is_folder, node.content_format)"></span></td>
|
||||
<td class="col-name" x-text="node.name"></td>
|
||||
<td style="font-size:12px;color:var(--text-tertiary)" x-text="node.is_folder ? '📁 Folder' : _fileTypeLabel(node.name, node.content_format)"></td>
|
||||
@@ -631,7 +638,7 @@
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<template x-for="node in displayTree" :key="'d'+node.id">
|
||||
<tr @click="node.is_folder ? navigateToFolder(node.id) : selectForPreview(node)" style="cursor:pointer"
|
||||
<tr :data-ws-id="node.id" @click="node.is_folder ? navigateToFolder(node.id) : selectForPreview(node)" @mouseenter="showPreview($event, node)" @mouseleave="hidePreview()" style="cursor:pointer"
|
||||
:class="{ 'selected-row': previewItem && previewItem.id === node.id }">
|
||||
<td><span class="table-icon" x-text="_fileIcon(node.name, node.is_folder, node.content_format)"></span></td>
|
||||
<td class="col-name" x-text="node.name"></td>
|
||||
@@ -655,7 +662,7 @@
|
||||
<!-- Title view — card grid -->
|
||||
<div class="title-grid" x-show="viewMode==='title' && !searchQuery && !filterType && !tagFilter && displayTree.length>0">
|
||||
<template x-for="node in displayTree" :key="'g'+node.id">
|
||||
<div class="title-card" @click="node.is_folder ? navigateToFolder(node.id) : selectForPreview(node)"
|
||||
<div class="title-card" :data-ws-id="node.id" @click="node.is_folder ? navigateToFolder(node.id) : selectForPreview(node)" @mouseenter="showPreview($event, node)" @mouseleave="hidePreview()"
|
||||
:class="{ 'selected-card': previewItem && previewItem.id === node.id }">
|
||||
<div class="title-card-icon" x-text="node.is_folder ? '📁' : _fileIcon(node.name, node.is_folder, node.content_format)"></div>
|
||||
<div class="title-card-name" x-text="node.name"></div>
|
||||
@@ -670,7 +677,7 @@
|
||||
<!-- Content view — list with inline preview -->
|
||||
<div class="content-list" x-show="viewMode==='content' && !searchQuery && !filterType && !tagFilter && displayTree.length>0">
|
||||
<template x-for="node in displayTree" :key="'c'+node.id">
|
||||
<div class="content-item" @click="node.is_folder ? navigateToFolder(node.id) : selectForPreview(node)"
|
||||
<div class="content-item" :data-ws-id="node.id" @click="node.is_folder ? navigateToFolder(node.id) : selectForPreview(node)" @mouseenter="showPreview($event, node)" @mouseleave="hidePreview()"
|
||||
:class="{ 'selected-row': previewItem && previewItem.id === node.id }">
|
||||
<div class="content-item-header">
|
||||
<span x-text="_fileIcon(node.name, node.is_folder, node.content_format)"></span>
|
||||
@@ -753,6 +760,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /ws-main -->
|
||||
|
||||
<!-- Context menu -->
|
||||
<div class="ctx-menu" x-show="ctxMenu.visible"
|
||||
:style="{ left: ctxMenu.x + 'px', top: ctxMenu.y + 'px' }"
|
||||
@@ -797,7 +806,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Preview Panel — right side panel for file preview -->
|
||||
<div class="preview-panel" x-show="previewItem && (viewMode==='list' || viewMode==='details' || viewMode==='title' || viewMode==='content')" x-transition:enter="preview-enter" x-transition:leave="preview-leave">
|
||||
<div class="preview-panel" x-show="previewItem" x-transition:enter="preview-enter" x-transition:leave="preview-leave">
|
||||
<div class="preview-panel-header">
|
||||
<span x-text="_fileIcon(previewItem.name, previewItem.is_folder, previewItem.content_format)"></span>
|
||||
<span class="preview-panel-title" x-text="previewItem.name"></span>
|
||||
@@ -821,7 +830,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div><!-- /ws-split -->
|
||||
|
||||
<script>
|
||||
document.addEventListener('alpine:init', function() {
|
||||
@@ -1190,25 +1199,14 @@ document.addEventListener('alpine:init', function() {
|
||||
|
||||
// ── Context menu ──
|
||||
onContextMenu(ev) {
|
||||
// Find the closest item element with data-ws-id or a clickable row
|
||||
var el = ev.target.closest('[data-ws-id], .ws-tree-item, .title-card, .content-item, tr[style*="cursor:pointer"]');
|
||||
// Find closest element with data-ws-id (works on all views)
|
||||
var el = ev.target.closest('[data-ws-id]');
|
||||
if (!el) { this.ctxMenu.visible = false; return; }
|
||||
// Get the node id
|
||||
var id = parseInt(el.getAttribute('data-ws-id') || el.getAttribute('data-folder-id'));
|
||||
if (!id) {
|
||||
// Try to find the node from displayTree by index match
|
||||
var rows = this.$el.querySelectorAll('tr[style*="cursor:pointer"], .title-card, .content-item');
|
||||
for (var i=0; i<rows.length; i++) {
|
||||
if (rows[i] === el || rows[i].contains(el)) {
|
||||
if (this.displayTree[i]) { id = this.displayTree[i].id; break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
var id = parseInt(el.getAttribute('data-ws-id'));
|
||||
if (!id) { this.ctxMenu.visible = false; return; }
|
||||
// Find node in tree
|
||||
var node = this._findNodeById(this.displayTree, id);
|
||||
if (!node) { this.ctxMenu.visible = false; return; }
|
||||
// Set context menu
|
||||
this.ctxMenu = {
|
||||
visible: true,
|
||||
x: Math.min(ev.clientX, window.innerWidth - 200),
|
||||
|
||||
Reference in New Issue
Block a user