clusev/resources/views/livewire/files/index.blade.php

133 lines
7.2 KiB
PHP

@php
$dirs = collect($entries)->where('type', 'dir')->count();
$files = collect($entries)->where('type', 'file')->count();
// Human-readable byte sizes for the live SFTP listing.
$fmtSize = function (?int $bytes): string {
if ($bytes === null) {
return '—';
}
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$i = 0;
$val = (float) $bytes;
while ($val >= 1024 && $i < count($units) - 1) {
$val /= 1024;
$i++;
}
return ($i === 0 ? (int) $val : number_format($val, 1)).' '.$units[$i];
};
@endphp
<div class="space-y-4" wire:init="load">
{{-- Header --}}
<div class="flex flex-wrap items-end justify-between gap-3">
<div>
<p class="font-mono text-[11px] uppercase tracking-[0.2em] text-accent-text">{{ __('files.eyebrow') }}</p>
<h2 class="mt-0.5 font-display text-xl font-semibold text-ink sm:text-2xl">{{ __('files.heading') }}</h2>
</div>
<x-status-pill :status="$connected ? 'online' : 'offline'">{{ $connected ? __('files.sftp_connected') : __('files.sftp_disconnected') }}</x-status-pill>
</div>
{{-- Breadcrumb / path --}}
<x-panel :padded="false">
<div class="flex flex-wrap items-center gap-x-1 gap-y-1.5 px-4 py-3 sm:px-5">
<x-icon name="folder" class="mr-1 h-4 w-4 shrink-0 text-ink-3" />
@foreach ($this->crumbs as $crumb)
@if (! $loop->first)
<span class="font-mono text-xs text-ink-4">/</span>
@endif
<button type="button" wire:click="go({{ $loop->index }})" @class([
'inline-flex min-h-11 min-w-11 items-center justify-center px-2 font-mono text-xs',
'text-ink-3' => $loop->last,
'text-accent-text hover:text-accent' => ! $loop->last,
])>{{ $crumb['label'] }}</button>
@endforeach
</div>
</x-panel>
{{-- Listing --}}
<x-panel :title="$path" :subtitle="__('files.panel_subtitle', ['dirs' => $dirs, 'files' => $files])" :padded="false">
<x-slot:actions>
<label class="inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-md border border-accent/25 bg-accent/10 px-3 text-xs font-medium text-accent-text transition-colors hover:bg-accent/15">
<x-icon name="plus" class="h-3.5 w-3.5" wire:loading.remove wire:target="upload" />
<svg wire:loading wire:target="upload" class="h-3.5 w-3.5 animate-spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M21 12a9 9 0 1 1-6.219-8.56" stroke-linecap="round" /></svg>
{{ __('files.upload') }}
<input type="file" wire:model="upload" class="hidden" />
</label>
</x-slot:actions>
{{-- Column header (desktop only) --}}
<div class="hidden border-b border-line px-4 py-2 sm:px-5 lg:grid lg:grid-cols-[minmax(0,1fr)_8rem_6rem_11rem_auto] lg:items-center lg:gap-4">
<span class="font-mono text-[10px] uppercase tracking-wider text-ink-4">{{ __('files.col_name') }}</span>
<span class="font-mono text-[10px] uppercase tracking-wider text-ink-4">{{ __('files.col_perms') }}</span>
<span class="text-right font-mono text-[10px] uppercase tracking-wider text-ink-4">{{ __('files.col_size') }}</span>
<span class="font-mono text-[10px] uppercase tracking-wider text-ink-4">{{ __('files.col_modified') }}</span>
<span class="sr-only">{{ __('common.actions') }}</span>
</div>
<div class="divide-y divide-line" wire:loading.class="opacity-40" wire:target="load,open,go,up">
@if (! $ready)
@for ($i = 0; $i < 8; $i++)
<div class="flex items-center gap-3 px-4 py-3 sm:px-5">
<x-skeleton class="h-8 w-8 rounded-sm" />
<x-skeleton class="h-3 w-48 max-w-full" />
<x-skeleton class="ml-auto hidden h-3 w-24 lg:block" />
</div>
@endfor
@else
@foreach ($entries as $e)
@php $isDir = $e['type'] === 'dir'; @endphp
<div class="group grid grid-cols-1 gap-x-4 gap-y-2 px-4 py-3 transition-colors hover:bg-raised sm:px-5 lg:grid-cols-[minmax(0,1fr)_8rem_6rem_11rem_auto] lg:items-center">
{{-- Name + icon (directories look clickable) --}}
<div class="flex min-w-0 items-center gap-3">
<span @class([
'grid h-8 w-8 shrink-0 place-items-center rounded-sm',
'bg-accent/10 text-accent-text' => $isDir,
'bg-raised text-ink-3' => ! $isDir,
])>
<x-icon :name="$isDir ? 'folder' : 'file'" class="h-4 w-4" />
</span>
@if ($isDir)
<button type="button" wire:click="open({{ $loop->index }})" class="min-w-0 flex-1 text-left">
<span class="truncate font-mono text-sm text-ink group-hover:text-accent-text">{{ $e['name'] }}/</span>
</button>
@else
<button type="button" wire:click="edit({{ $loop->index }})" class="min-w-0 flex-1 truncate text-left font-mono text-sm text-ink-2 transition-colors hover:text-accent-text">{{ $e['name'] }}</button>
@endif
</div>
{{-- Permissions (hidden on mobile; shown inline below name on tablet+) --}}
<p class="hidden font-mono text-xs text-ink-3 lg:block">{{ $e['perms'] }}</p>
{{-- Size (hidden on mobile) --}}
<p class="hidden font-mono text-xs text-ink-3 sm:block lg:text-right">{{ $fmtSize($e['size']) }}</p>
{{-- Modified --}}
<p class="hidden font-mono text-xs text-ink-3 sm:block">{{ $e['modified'] }}</p>
{{-- Compact meta line for mobile (perms · size · modified) --}}
<p class="flex flex-wrap items-center gap-x-2 font-mono text-[11px] text-ink-4 sm:hidden">
<span>{{ $e['perms'] }}</span>
<span>·</span>
<span>{{ $fmtSize($e['size']) }}</span>
<span>·</span>
<span>{{ $e['modified'] }}</span>
</p>
{{-- Row actions --}}
<div class="flex shrink-0 items-center gap-1 lg:justify-end">
@unless ($isDir)
<x-btn variant="secondary" wire:click="download({{ $loop->index }})">{{ __('files.download') }}</x-btn>
<x-btn variant="secondary" wire:click="edit({{ $loop->index }})">{{ __('common.edit') }}</x-btn>
@endunless
<x-btn variant="danger-soft" wire:click="confirmDelete({{ $loop->index }})">{{ __('common.delete') }}</x-btn>
</div>
</div>
@endforeach
@endif
</div>
</x-panel>
</div>