Add the "move nodes" feature to TreeView component

This commit is contained in:
brufdev
2025-02-23 18:44:15 +00:00
parent 17559d646d
commit d52a66ed2e
8 changed files with 116 additions and 32 deletions

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Livewire\Vault;
use App\Actions\UpdateVaultNode;
use App\Models\Vault;
use App\Models\VaultNode;
use Illuminate\Contracts\View\Factory;
@@ -17,6 +18,36 @@ final class TreeView extends Component
{
public Vault $vault;
public function moveNode(VaultNode $source, ?VaultNode $target): void
{
$this->authorize('view', $source->vault);
/** @var Vault $sourceVault */
$sourceVault = $source->vault;
/** @var VaultNode $target */
if ($target->exists && !$sourceVault->is($target->vault)) {
abort(403);
}
$parentId = null;
if ($target->exists) {
// Ignore if $target is a child of $source
if ($target->ancestors->pluck('id')->contains($source->id)) {
return;
}
$parentId = $target->is_file ? $target->parent_id : $target->id;
}
if ($source->parent_id === $parentId) {
return;
}
new UpdateVaultNode()->handle($source, ['parent_id' => $parentId]);
}
public function placeholder(): string
{
return <<<'HTML'

View File

@@ -1,3 +1,30 @@
<div class="flex flex-grow w-full">
<div class="flex flex-col flex-grow w-full" x-data="treeView"
@treeview-enter-node.window="enterNode($event.detail.event)"
@treeview-leave-node.window="leaveNode($event.detail.event)"
@treeview-move-node.window="moveNode($event.detail.event)"
>
{{ $slot }}
</div>
@script
<script>
Alpine.data('treeView', () => ({
moveNode(event) {
const sourceId = event.dataTransfer.getData('text/plain');
const targetId = event.target.closest('a').dataset.id;
if (!+sourceId || (targetId.length && !+targetId) || sourceId == targetId) {
return;
}
const args = [sourceId];
if (targetId.length) {
args.push(targetId);
}
$wire.moveNode(...args);
}
}));
</script>
@endscript

View File

@@ -1,3 +1,12 @@
<li x-data="{ accordionOpen: false }" class="items-center justify-between py-0.5" {{ $attributes }}>
@props(['node'])
<li class="items-center justify-between XXXpy-0.5 my-0.5" draggable="true"
x-data="{ accordionOpen: false }"
{{ $attributes }}
@dragstart.stop="event.dataTransfer.setData('text/plain', '{{ $node->id }}')"
@dragenter.prevent=""
@dragover.prevent=""
@drop="moveNode(event)"
>
{{ $slot }}
</li>

View File

@@ -1,19 +1,22 @@
@props(['node'])
@aware(['node'])
<div class="relative w-full">
<x-menu>
<button x-ref="button" @click="openFile({{ $node->id }})"
@contextmenu.prevent="menuOpen = !menuOpen" @keydown.escape="menuOpen = false"
@auxclick.outside="menuOpen = false" class="flex items-center w-full">
<span class="flex items-center w-full gap-2">
<span title="{{ $node->name }}" class="ml-1 overflow-hidden whitespace-nowrap text-ellipsis">
{{ $node->name }}
</span>
@if (!in_array($node->extension, App\Services\VaultFiles\Note::extensions()))
<x-treeView.badge>{{ $node->extension }}</x-treeView.badge>
@endif
<a href="" class="flex items-center w-full gap-2" title="{{ $node->name }}"
data-id="{{ $node->id }}" x-ref="button"
@click.prevent="openFile({{ $node->id }})"
@contextmenu.prevent="menuOpen = !menuOpen"
@keydown.escape="menuOpen = false"
@auxclick.outside="menuOpen = false"
>
<span class="ml-1 overflow-hidden whitespace-nowrap text-ellipsis">
{{ $node->name }}
</span>
</button>
@if (!in_array($node->extension, App\Services\VaultFiles\Note::extensions()))
<x-treeView.badge>{{ $node->extension }}</x-treeView.badge>
@endif
</a>
<x-menu.items>
<x-menu.close>
@@ -22,8 +25,10 @@
{{ __('Rename') }}
</x-menu.item>
<x-menu.item wire:confirm="{{ __('Are you sure you want to delete this file?') }}"
wire:click="$parent.deleteNode({{ $node->id }})">
<x-menu.item
wire:confirm="{{ __('Are you sure you want to delete this file?') }}"
wire:click="$parent.deleteNode({{ $node->id }})"
>
<x-icons.trash class="w-4 h-4" />
{{ __('Delete') }}
</x-menu.item>

View File

@@ -1,18 +1,21 @@
@props(['node'])
@aware(['node'])
<div class="relative w-full">
<x-menu>
<button x-ref="button" @click="accordionOpen = !accordionOpen" @contextmenu.prevent="menuOpen = !menuOpen"
@keydown.escape="menuOpen = false" @auxclick.outside="menuOpen = false" class="flex items-center w-full">
<span class="flex items-center w-full">
<x-icons.chevronRight x-show="!accordionOpen" class="w-4 h-4" />
<x-icons.chevronDown x-show="accordionOpen" class="w-4 h-4" x-cloak />
<a href="" class="flex items-center w-full" title="{{ $node->name }}"
data-id="{{ $node->id }}" x-ref="button"
@click.prevent="accordionOpen = !accordionOpen"
@contextmenu.prevent="menuOpen = !menuOpen"
@keydown.escape="menuOpen = false"
@auxclick.outside="menuOpen = false"
>
<x-icons.chevronRight x-show="!accordionOpen" class="w-4 h-4" />
<x-icons.chevronDown x-show="accordionOpen" class="w-4 h-4" x-cloak />
<span title="{{ $node->name }}" class="ml-1 overflow-hidden whitespace-nowrap text-ellipsis">
{{ $node->name }}
</span>
<span class="ml-1 overflow-hidden whitespace-nowrap text-ellipsis">
{{ $node->name }}
</span>
</button>
</a>
<x-menu.items>
<x-menu.close>

View File

@@ -1,8 +1,9 @@
@props(['root'])
<ul @unless ($root)
<ul class="relative w-full pl-4 first:pl-0"
@unless ($root)
x-show="accordionOpen" x-collapse x-cloak
@endunless
class="relative w-full pl-4 first:pl-0">
>
{{ $slot }}
</ul>

View File

@@ -1,13 +1,13 @@
@props(['node'])
<x-treeView.item>
<x-treeView.item :$node>
@if (!$node->is_file)
<x-treeView.itemFolder :$node />
<x-treeView.itemFolder />
@if (!empty($node->children) && $node->children->count())
@include('components.vault.treeViewNode', ['nodes' => $node->children, 'root' => false])
@endif
@else
<x-treeView.itemFile :$node />
<x-treeView.itemFile />
@endif
</x-treeView.item>

View File

@@ -46,7 +46,15 @@
:class="{ 'translate-x-0': isLeftPanelOpen, '-translate-x-full hidden': !isLeftPanelOpen }"
>
<div class="sticky top-0 z-[5] flex justify-between p-4 bg-light-base-50 dark:bg-base-900">
<h3>{{ $vault->name }}</h3>
<h3>
<a data-id="" draggable="true"
@dragenter.prevent=""
@dragover.prevent=""
@drop="$dispatch('treeview-move-node', { event: event })"
>
{{ $vault->name }}
</a>
</h3>
<div class="flex items-center">
<x-menu>