mirror of
https://github.com/brufdev/many-notes.git
synced 2026-01-25 12:28:58 -06:00
Add the "move nodes" feature to TreeView component
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user