gui: sidebar tree model fix — only container nodes, improved DnD + context menu
- Backend: ListWorkspaceTree/ListWorkspaceChildren filter to container types only (case, client, project, folder, document, recipe) - TreeNode: full-row context menu (removed label stopPropagation), double-click toggles expand, icon-click toggles expand, DnD auto-expand on 500ms hover, auto-scroll near edges, drag-over highlight via classList - App.svelte: toggleExpand uses ListWorkspaceChildren, submitCreateNode uses ListWorkspaceChildren for child tree population - Note/file nodes no longer appear in the sidebar workspace tree
This commit is contained in:
parent
b2dcb116c9
commit
9260582072
|
|
@ -19,7 +19,26 @@ func (a *App) ListWorkspaceTree() ([]NodeDTO, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toNodeDTOs(list), nil
|
||||
return filterContainers(toNodeDTOs(list)), nil
|
||||
}
|
||||
|
||||
func (a *App) ListWorkspaceChildren(parentID string) ([]NodeDTO, error) {
|
||||
list, err := a.nodes.ListChildren(parentID, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return filterContainers(toNodeDTOs(list)), nil
|
||||
}
|
||||
|
||||
func filterContainers(dtos []NodeDTO) []NodeDTO {
|
||||
out := make([]NodeDTO, 0, len(dtos))
|
||||
for _, d := range dtos {
|
||||
switch d.Type {
|
||||
case "case", "client", "project", "folder", "document", "recipe":
|
||||
out = append(out, d)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (a *App) ListChildren(parentID string) ([]NodeDTO, error) {
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -16,8 +16,8 @@
|
|||
background: #13131f;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/main-CBFn_7kC.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-C49DuBq6.css">
|
||||
<script type="module" crossorigin src="/assets/main-Dt9BLCiY.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-CHi9sCXv.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
|
|
|||
|
|
@ -643,7 +643,7 @@
|
|||
createWithTemplate = undefined
|
||||
if (parentID) {
|
||||
expanded = { ...expanded, [parentID]: true }
|
||||
const children = await wailsCall('ListChildren', parentID) || []
|
||||
const children = await wailsCall('ListWorkspaceChildren', parentID) || []
|
||||
setNodeChildren(workspaceTree, parentID, children)
|
||||
workspaceTree = [...workspaceTree]
|
||||
} else {
|
||||
|
|
@ -673,7 +673,7 @@
|
|||
const willExpand = !expanded[nodeId]
|
||||
expanded = { ...expanded, [nodeId]: willExpand }
|
||||
if (!willExpand) return
|
||||
const children = await wailsCall('ListChildren', nodeId) || []
|
||||
const children = await wailsCall('ListWorkspaceChildren', nodeId) || []
|
||||
setNodeChildren(workspaceTree, nodeId, children)
|
||||
workspaceTree = [...workspaceTree]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@
|
|||
export let onContextMenu
|
||||
export let onDrop
|
||||
|
||||
let autoExpandTimers = {}
|
||||
let scrollInterval = null
|
||||
|
||||
function iconKind(node) {
|
||||
if (node.type === 'client' || node.template_id === 'client.default') return 'client'
|
||||
if (node.type === 'project' || node.template_id === 'project.default') return 'project'
|
||||
|
|
@ -63,15 +66,69 @@
|
|||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
e.dataTransfer.dropEffect = 'move'
|
||||
e.currentTarget.classList.add('drag-over')
|
||||
if (isContainer(node) && !expanded[node.id] && !autoExpandTimers[node.id]) {
|
||||
autoExpandTimers[node.id] = setTimeout(() => {
|
||||
if (onToggle) onToggle(node.id)
|
||||
delete autoExpandTimers[node.id]
|
||||
}, 500)
|
||||
}
|
||||
const rect = e.currentTarget.closest('.workspace-tree-area') || document.querySelector('.workspace-tree-area')
|
||||
if (rect) {
|
||||
const areaRect = rect.getBoundingClientRect()
|
||||
const threshold = 30
|
||||
if (e.clientY - areaRect.top < threshold) {
|
||||
if (!scrollInterval) {
|
||||
scrollInterval = setInterval(() => { rect.scrollTop -= 10 }, 50)
|
||||
}
|
||||
} else if (areaRect.bottom - e.clientY < threshold) {
|
||||
if (!scrollInterval) {
|
||||
scrollInterval = setInterval(() => { rect.scrollTop += 10 }, 50)
|
||||
}
|
||||
} else {
|
||||
if (scrollInterval) { clearInterval(scrollInterval); scrollInterval = null }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleDragLeave(e, node) {
|
||||
e.currentTarget.classList.remove('drag-over')
|
||||
if (autoExpandTimers[node.id]) {
|
||||
clearTimeout(autoExpandTimers[node.id])
|
||||
delete autoExpandTimers[node.id]
|
||||
}
|
||||
if (scrollInterval) { clearInterval(scrollInterval); scrollInterval = null }
|
||||
}
|
||||
|
||||
function handleDrop(e, node) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
e.currentTarget.classList.remove('drag-over')
|
||||
if (autoExpandTimers[node.id]) {
|
||||
clearTimeout(autoExpandTimers[node.id])
|
||||
delete autoExpandTimers[node.id]
|
||||
}
|
||||
if (scrollInterval) { clearInterval(scrollInterval); scrollInterval = null }
|
||||
const draggedId = getDraggedId(e)
|
||||
if (!canDrop(node, draggedId)) return
|
||||
if (onDrop) onDrop(draggedId, node.id)
|
||||
}
|
||||
|
||||
function handleRowClick(e, node) {
|
||||
const toggleEl = e.target.closest('.tree-toggle')
|
||||
const iconEl = e.target.closest('.tree-icon')
|
||||
if (toggleEl || iconEl) return
|
||||
if (onSelect) onSelect(node)
|
||||
}
|
||||
|
||||
function handleRowDblClick(e, node) {
|
||||
if (isContainer(node) && onToggle) onToggle(node.id)
|
||||
}
|
||||
|
||||
function handleIconClick(e, node) {
|
||||
e.stopPropagation()
|
||||
if (isContainer(node) && onToggle) onToggle(node.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each nodes as node}
|
||||
|
|
@ -81,23 +138,22 @@
|
|||
draggable="true"
|
||||
on:dragstart={(e) => handleDragStart(e, node)}
|
||||
on:dragover={(e) => handleDragOver(e, node)}
|
||||
on:dragleave={(e) => handleDragLeave(e, node)}
|
||||
on:drop={(e) => handleDrop(e, node)}
|
||||
on:click={(e) => handleRowClick(e, node)}
|
||||
on:dblclick={(e) => handleRowDblClick(e, node)}
|
||||
on:contextmenu|preventDefault={(e) => onContextMenu && onContextMenu(e, node)}>
|
||||
{#if isContainer(node)}
|
||||
<button class="tree-toggle" on:click|stopPropagation={() => onToggle && onToggle(node.id)}
|
||||
on:contextmenu|preventDefault={(e) => e.stopPropagation()}>
|
||||
{#if expanded[node.id]}
|
||||
<span class="tree-arrow">▾</span>
|
||||
{:else}
|
||||
<span class="tree-arrow">▸</span>
|
||||
{/if}
|
||||
<button class="tree-toggle" on:click|stopPropagation={() => onToggle && onToggle(node.id)}>
|
||||
<span class="tree-arrow">{expanded[node.id] ? '▾' : '▸'}</span>
|
||||
</button>
|
||||
{:else}
|
||||
<span class="tree-toggle-placeholder"></span>
|
||||
{/if}
|
||||
<span class="tree-icon"><TemplateIcon kind={iconKind(node)} size={16} /></span>
|
||||
<span class="tree-label" on:click|stopPropagation={() => onSelect && onSelect(node)}
|
||||
on:contextmenu|preventDefault={(e) => e.stopPropagation()}>
|
||||
<span class="tree-icon" on:click={(e) => handleIconClick(e, node)} on:dblclick|stopPropagation>
|
||||
<TemplateIcon kind={iconKind(node)} size={16} />
|
||||
</span>
|
||||
<span class="tree-label" on:click|stopPropagation={() => onSelect && onSelect(node)}>
|
||||
{node.title}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -126,6 +182,11 @@
|
|||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
.tree-item.drag-over {
|
||||
background: #1a3a1a;
|
||||
outline: 1px solid #4ade80;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
.tree-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
|
|
@ -143,7 +204,7 @@
|
|||
font-size: 12px;
|
||||
}
|
||||
.tree-toggle:hover {
|
||||
color: #888;
|
||||
color: #a5b4fc;
|
||||
}
|
||||
.tree-toggle-placeholder {
|
||||
display: inline-block;
|
||||
|
|
@ -159,6 +220,10 @@
|
|||
flex-shrink: 0;
|
||||
color: #888;
|
||||
margin-right: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tree-icon:hover {
|
||||
color: #a5b4fc;
|
||||
}
|
||||
.tree-item.selected .tree-icon {
|
||||
color: #a5b4fc;
|
||||
|
|
|
|||
Loading…
Reference in New Issue