Reimplement Drag & Drop for file uploading 🎉 (#1858)

This commit is contained in:
Charles 2025-11-09 09:24:12 -05:00 committed by GitHub
parent 172436e012
commit 0891db5342
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 1072 additions and 172 deletions

View File

@ -12,8 +12,10 @@ use App\Models\File;
use App\Models\Permission;
use App\Models\Server;
use App\Repositories\Daemon\DaemonFileRepository;
use App\Services\Nodes\NodeJWTService;
use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Carbon\CarbonImmutable;
use Exception;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
@ -25,7 +27,6 @@ use Filament\Actions\EditAction;
use Filament\Facades\Filament;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\CodeEditor;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
@ -34,8 +35,6 @@ use Filament\Panel;
use Filament\Resources\Pages\ListRecords;
use Filament\Resources\Pages\PageRegistration;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Support\Enums\IconSize;
use Filament\Support\Facades\FilamentView;
@ -43,7 +42,7 @@ use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Enums\PaginationMode;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\UploadedFile;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Routing\Route;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Route as RouteFacade;
@ -56,6 +55,8 @@ class ListFiles extends ListRecords
protected static string $resource = FileResource::class;
protected string $view = 'filament.server.pages.list-files';
#[Locked]
public string $path = '/';
@ -504,7 +505,7 @@ class ListFiles extends ListRecords
->color('primary')
->action(function ($data) {
try {
$this->getDaemonFileRepository()->createDirectory($data['name'], $this->path);
$this->createFolder($data['name']);
Activity::event('server:file.create-directory')
->property(['directory' => $this->path, 'name' => $data['name']])
@ -528,58 +529,30 @@ class ListFiles extends ListRecords
->label(trans('server/file.actions.new_folder.folder_name'))
->required(),
]),
Action::make('upload')
Action::make('uploadFile')
->authorize(fn () => user()?->can(Permission::ACTION_FILE_CREATE, $server))
->hiddenLabel()->icon('tabler-upload')->iconButton()->iconSize(IconSize::ExtraLarge)
->tooltip(trans('server/file.actions.upload.title'))
->view('filament.server.pages.file-upload'),
Action::make('uploadURL')
->authorize(fn () => user()?->can(Permission::ACTION_FILE_CREATE, $server))
->hiddenLabel()->icon('tabler-download')->iconButton()->iconSize(IconSize::ExtraLarge)
->tooltip(trans('server/file.actions.upload.from_url'))
->modalHeading(trans('server/file.actions.upload.from_url'))
->color('success')
->action(function ($data) {
if (count($data['files']) > 0 && !isset($data['url'])) {
/** @var UploadedFile $file */
foreach ($data['files'] as $file) {
$this->getDaemonFileRepository()->putContent(join_paths($this->path, $file->getClientOriginalName()), $file->getContent());
$this->getDaemonFileRepository()->pull($data['url'], $this->path);
Activity::event('server:file.uploaded')
->property('directory', $this->path)
->property('file', $file->getClientOriginalName())
->log();
}
} elseif ($data['url'] !== null) {
$this->getDaemonFileRepository()->pull($data['url'], $this->path);
Activity::event('server:file.pull')
->property('url', $data['url'])
->property('directory', $this->path)
->log();
}
Activity::event('server:file.pull')
->property('url', $data['url'])
->property('directory', $this->path)
->log();
$this->refreshPage();
})
->schema([
Tabs::make()
->contained(false)
->schema([
Tab::make('files')
->label(trans('server/file.actions.upload.from_files'))
->live()
->schema([
FileUpload::make('files')
->storeFiles(false)
->previewable(false)
->preserveFilenames()
->maxSize((int) round($server->node->upload_size * (config('panel.use_binary_prefix') ? 1.048576 * 1024 : 1000)))
->multiple(),
]),
Tab::make('url')
->label(trans('server/file.actions.upload.url'))
->live()
->disabled(fn (Get $get) => count($get('files')) > 0)
->schema([
TextInput::make('url')
->label(trans('server/file.actions.upload.url'))
->url(),
]),
]),
TextInput::make('url')
->label(trans('server/file.actions.upload.url'))
->required()
->url(),
]),
Action::make('search')
->authorize(fn () => user()?->can(Permission::ACTION_FILE_READ, $server))
@ -627,6 +600,81 @@ class ListFiles extends ListRecords
};
}
public function getUploadUrl(NodeJWTService $jwtService): string
{
/** @var Server $server */
$server = Filament::getTenant();
if (!user()?->can(Permission::ACTION_FILE_CREATE, $server)) {
abort(403, 'You do not have permission to upload files.');
}
$token = $jwtService
->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
->setUser(user())
->setClaims(['server_uuid' => $server->uuid])
->handle($server->node, user()->id . $server->uuid);
return sprintf(
'%s/upload/file?token=%s',
$server->node->getConnectionAddress(),
$token->toString()
);
}
public function getUploadSizeLimit(): int
{
/** @var Server $server */
$server = Filament::getTenant();
return $server->node->upload_size * 1024 * 1024;
}
/**
* @throws ConnectionException
* @throws FileExistsException
* @throws \Throwable
*/
public function createFolder(string $folderPath): void
{
/** @var Server $server */
$server = Filament::getTenant();
if (!user()?->can(Permission::ACTION_FILE_CREATE, $server)) {
abort(403, 'You do not have permission to create folders.');
}
try {
$this->getDaemonFileRepository()->createDirectory($folderPath, $this->path);
Activity::event('server:file.create-directory')
->property(['directory' => $this->path, 'name' => $folderPath])
->log();
} catch (FileExistsException) {
// Ignore if the folder already exists.
} catch (ConnectionException $e) {
Notification::make()
->body($e->getMessage())
->danger()
->send();
}
}
/**
* @param string[] $files
*/
public function logUploadedFiles(array $files): void
{
$filesCollection = collect($files);
Activity::event('server:files.uploaded')
->property('directory', $this->path)
->property('files', $filesCollection)
->log();
}
private function getDaemonFileRepository(): DaemonFileRepository
{
/** @var Server $server */

View File

@ -17,6 +17,11 @@ return [
'from_files' => 'Upload Files',
'from_url' => 'Upload from URL',
'url' => 'URL',
'drop_files' => 'Drop files to upload',
'success' => 'Files uploaded successfully',
'failed' => 'Failed to upload files',
'header' => 'Uploading Files',
'error' => 'An error occurred while uploading',
],
'rename' => [
'title' => 'Rename',

View File

@ -14,7 +14,7 @@
"laravel-vite-plugin": "^1.0",
"prettier": "^3.4.2",
"tailwindcss": "^4.1.4",
"vite": "6.2.6"
"vite": "7.1.11"
},
"dependencies": {
"@xterm/addon-fit": "^0.10.0",

View File

@ -0,0 +1,373 @@
<div
x-data="{
isUploading: false,
uploadQueue: [],
currentFileIndex: 0,
totalFiles: 0,
autoCloseTimer: 1000,
async extractFilesFromItems(items) {
const filesWithPaths = [];
const traversePromises = [];
for (let i = 0; i < items.length; i++) {
const entry = items[i].webkitGetAsEntry?.();
if (entry) {
traversePromises.push(this.traverseFileTree(entry, '', filesWithPaths));
} else if (items[i].kind === 'file') {
const file = items[i].getAsFile();
if (file) {
filesWithPaths.push({
file: file,
path: '',
});
}
}
}
await Promise.all(traversePromises);
return filesWithPaths;
},
async traverseFileTree(entry, path, filesWithPaths) {
return new Promise((resolve) => {
if (entry.isFile) {
entry.file((file) => {
filesWithPaths.push({
file: file,
path: path,
});
resolve();
});
} else if (entry.isDirectory) {
const reader = entry.createReader();
const readEntries = () => {
reader.readEntries(async (entries) => {
if (entries.length === 0) {
resolve();
return;
}
const subPromises = entries.map((e) =>
this.traverseFileTree(
e,
path ? `${path}/${entry.name}` : entry.name,
filesWithPaths
)
);
await Promise.all(subPromises);
readEntries();
});
};
readEntries();
} else {
resolve();
}
});
},
async uploadFilesWithFolders(filesWithPaths) {
this.isUploading = true;
this.uploadQueue = [];
this.totalFiles = filesWithPaths.length;
this.currentFileIndex = 0;
const uploadedFiles = [];
try {
const uploadSizeLimit = await $wire.getUploadSizeLimit();
for (const {
file
}
of filesWithPaths) {
if (file.size > uploadSizeLimit) {
new window.FilamentNotification()
.title(`File ${file.name} exceeds the upload size limit of ${this.formatBytes(uploadSizeLimit)}`)
.danger()
.send();
this.isUploading = false;
return;
}
}
const folderPaths = new Set();
for (const {
path
}
of filesWithPaths) {
if (path) {
const parts = path.split('/').filter(Boolean);
let currentPath = '';
for (const part of parts) {
currentPath += part + '/';
folderPaths.add(currentPath);
}
}
}
for (const folderPath of folderPaths) {
try {
await $wire.createFolder(folderPath.slice(0, -1));
} catch (error) {
console.warn(`Folder ${folderPath} already exists or failed to create.`);
}
}
for (const f of filesWithPaths) {
this.uploadQueue.push({
file: f.file,
name: f.file.name,
path: f.path,
size: f.file.size,
progress: 0,
speed: 0,
uploadedBytes: 0,
status: 'pending',
error: null
});
}
const maxConcurrent = 3;
let activeUploads = [];
let completedCount = 0;
for (let i = 0; i < this.uploadQueue.length; i++) {
const uploadPromise = this.uploadFile(i)
.then(() => {
completedCount++;
this.currentFileIndex = completedCount;
const item = this.uploadQueue[i];
const relativePath = (item.path ? item.path.replace(/^\/+/, '') + '/' : '') + item.name;
uploadedFiles.push(relativePath);
})
.catch(() => {
completedCount++;
this.currentFileIndex = completedCount;
});
activeUploads.push(uploadPromise);
if (activeUploads.length >= maxConcurrent) {
await Promise.race(activeUploads);
activeUploads = activeUploads.filter(p => p.status !== 'fulfilled' && p.status !== 'rejected');
}
}
await Promise.allSettled(activeUploads);
const failed = this.uploadQueue.filter(f => f.status === 'error');
await $wire.$refresh();
if (failed.length === 0) {
new window.FilamentNotification()
.title('{{ trans('server/file.actions.upload.success') }}')
.success()
.send();
} else if (failed.length === this.totalFiles) {
new window.FilamentNotification()
.title('{{ trans('server/file.actions.upload.failed') }}')
.danger()
.send();
} else {
new window.FilamentNotification()
.title('{{ trans('server/file.actions.upload.failed') }}')
.danger()
.send();
}
if (uploadedFiles.length > 0) {
this.$nextTick(() => {
if (typeof $wire !== 'undefined' && $wire && typeof $wire.call === 'function') {
$wire.call('logUploadedFiles', uploadedFiles);
} else if (typeof window.livewire !== 'undefined' && typeof window.livewire.call === 'function') {
window.livewire.call('logUploadedFiles', uploadedFiles);
} else if (typeof Livewire !== 'undefined' && typeof Livewire.call === 'function') {
Livewire.call('logUploadedFiles', uploadedFiles);
} else {
console.warn('Could not call Livewire method logUploadedFiles; Livewire not found.');
}
});
}
if (this.autoCloseTimer) clearTimeout(this.autoCloseTimer);
this.autoCloseTimer = setTimeout(() => {
this.isUploading = false;
this.uploadQueue = [];
}, 1000);
} catch (error) {
console.error('Upload error:', error);
new window.FilamentNotification()
.title('{{ trans('server/file.actions.upload.error') }}')
.danger()
.send();
this.isUploading = false;
}
},
async uploadFile(index) {
const fileData = this.uploadQueue[index];
fileData.status = 'uploading';
try {
const uploadUrl = await $wire.getUploadUrl();
const url = new URL(uploadUrl);
let basePath = @js($this->path);
if (fileData.path && fileData.path.trim() !== '') {
basePath = basePath.replace(/\/+$/, '') + '/' + fileData.path.replace(/^\/+/, '');
}
url.searchParams.append('directory', basePath);
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('files', fileData.file);
let lastLoaded = 0;
let lastTime = Date.now();
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
fileData.uploadedBytes = e.loaded;
fileData.progress = Math.round((e.loaded / e.total) * 100);
const now = Date.now();
const timeDiff = (now - lastTime) / 1000;
if (timeDiff > 0.1) {
const bytesDiff = e.loaded - lastLoaded;
fileData.speed = bytesDiff / timeDiff;
lastTime = now;
lastLoaded = e.loaded;
}
}
});
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
fileData.status = 'complete';
fileData.progress = 100;
resolve();
} else {
fileData.status = 'error';
fileData.error = `Upload failed (${xhr.status})`;
reject(new Error(fileData.error));
}
};
xhr.onerror = () => {
fileData.status = 'error';
fileData.error = 'Network error';
reject(new Error('Network error'));
};
xhr.open('POST', url.toString());
xhr.send(formData);
});
} catch (err) {
fileData.status = 'error';
fileData.error = 'Failed to get upload token';
throw err;
}
},
formatBytes(bytes) {
if (bytes === 0) return '0.00 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
},
formatSpeed(bytesPerSecond) {
return this.formatBytes(bytesPerSecond) + '/s';
},
handleEscapeKey(e) {
if (e.key === 'Escape' && this.isUploading) {
this.isUploading = false;
this.uploadQueue = [];
}
},
async handleFileSelect(e) {
const files = Array.from(e.target.files);
if (files.length > 0) {
const filesWithPaths = files.map(f => ({
file: f,
path: ''
}));
await this.uploadFilesWithFolders(filesWithPaths);
}
},
triggerBrowse() {
this.$refs.fileInput.click();
},
}"
>
<x-filament::icon-button
iconSize="xl"
icon="tabler-upload"
color="success"
tooltip="{{ trans('server/file.actions.upload.title') }}"
@click="triggerBrowse">
</x-filament::icon-button>
<input type="file" x-ref="fileInput" class="hidden" multiple @change="handleFileSelect">
<div
x-show="isUploading"
x-cloak
class="fixed inset-0 z-50 flex items-center justify-center bg-gray-900/50 dark:bg-gray-100/20 p-4"
>
<div
class="rounded-lg bg-white shadow-xl dark:bg-gray-800 max-w-1/2 max-h-[50vh] overflow-hidden flex flex-col">
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-center">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
{{ trans('server/file.actions.upload.header') }} -
<span class="text-lg text-gray-600 dark:text-gray-400">
<span x-text="currentFileIndex"></span> of <span x-text="totalFiles"></span>
</span>
</h3>
</div>
<div class="flex-1 overflow-y-auto">
<div class="overflow-hidden">
<table class="w-full divide-y divide-gray-200 dark:divide-white/5">
<tbody class="divide-y divide-gray-200 dark:divide-white/5 bg-white dark:bg-gray-900">
<template x-for="(fileData, index) in uploadQueue" :key="index">
<tr class="transition duration-75 hover:bg-gray-50 dark:hover:bg-white/5">
<td class="px-4 py-4 sm:px-6">
<div class="flex flex-col gap-y-1">
<div
class="text-sm font-medium leading-6 text-gray-950 dark:text-white truncate max-w-xs"
x-text="(fileData.path ? fileData.path + '/' : '') + fileData.name">
</div>
<div x-show="fileData.status === 'error'"
class="text-xs text-danger-600 dark:text-danger-400"
x-text="fileData.error"></div>
</div>
</td>
<td class="px-4 py-4 sm:px-6">
<div class="text-sm text-gray-500 dark:text-gray-400"
x-text="formatBytes(fileData.size)"></div>
</td>
<td class="px-4 py-4 sm:px-6">
<div x-show="fileData.status === 'uploading' || fileData.status === 'complete'"
class="flex justify-between items-center text-sm">
<span class="font-medium text-gray-700 dark:text-gray-300"
x-text="`${fileData.progress}%`"></span>
<span x-show="fileData.status === 'uploading' && fileData.speed > 0"
class="text-gray-500 dark:text-gray-400"
x-text="formatSpeed(fileData.speed)"></span>
</div>
<span x-show="fileData.status === 'pending'"
class="text-sm text-gray-500 dark:text-gray-400">
</span>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,441 @@
<x-filament-panels::page>
<div
x-data="
{
isDragging: false,
dragCounter: 0,
isUploading: false,
uploadQueue: [],
currentFileIndex: 0,
totalFiles: 0,
autoCloseTimer: 1000,
handleDragEnter(e) {
e.preventDefault();
e.stopPropagation();
this.dragCounter++;
this.isDragging = true;
},
handleDragLeave(e) {
e.preventDefault();
e.stopPropagation();
this.dragCounter--;
if (this.dragCounter === 0) this.isDragging = false;
},
handleDragOver(e) {
e.preventDefault();
e.stopPropagation();
},
async handleDrop(e) {
e.preventDefault();
e.stopPropagation();
this.isDragging = false;
this.dragCounter = 0;
const items = e.dataTransfer.items;
const files = e.dataTransfer.files;
if ((!items || items.length === 0) && (!files || files.length === 0)) return;
let filesWithPaths = [];
if (items && items.length > 0 && items[0].webkitGetAsEntry) {
filesWithPaths = await this.extractFilesFromItems(items);
}
if (files && files.length > 0 && filesWithPaths.length === 0) {
filesWithPaths = Array.from(files).map(f => ({
file: f,
path: ''
}));
}
if (filesWithPaths.length > 0) {
await this.uploadFilesWithFolders(filesWithPaths);
}
},
async extractFilesFromItems(items) {
const filesWithPaths = [];
const traversePromises = [];
for (let i = 0; i < items.length; i++) {
const entry = items[i].webkitGetAsEntry?.();
if (entry) {
traversePromises.push(this.traverseFileTree(entry, '', filesWithPaths));
} else if (items[i].kind === 'file') {
const file = items[i].getAsFile();
if (file) {
filesWithPaths.push({
file: file,
path: '',
});
}
}
}
await Promise.all(traversePromises);
return filesWithPaths;
},
async traverseFileTree(entry, path, filesWithPaths) {
return new Promise((resolve) => {
if (entry.isFile) {
entry.file((file) => {
filesWithPaths.push({
file: file,
path: path,
});
resolve();
});
} else if (entry.isDirectory) {
const reader = entry.createReader();
const readEntries = () => {
reader.readEntries(async (entries) => {
if (entries.length === 0) {
resolve();
return;
}
const subPromises = entries.map((e) =>
this.traverseFileTree(
e,
path ? `${path}/${entry.name}` : entry.name,
filesWithPaths
)
);
await Promise.all(subPromises);
readEntries();
});
};
readEntries();
} else {
resolve();
}
});
},
async uploadFilesWithFolders(filesWithPaths) {
this.isUploading = true;
this.uploadQueue = [];
this.totalFiles = filesWithPaths.length;
this.currentFileIndex = 0;
const uploadedFiles = [];
try {
const uploadSizeLimit = await $wire.getUploadSizeLimit();
for (const {
file
}
of filesWithPaths) {
if (file.size > uploadSizeLimit) {
new window.FilamentNotification()
.title(`File ${file.name} exceeds the upload limit.`)
.danger()
.send();
this.isUploading = false;
return;
}
}
const folderPaths = new Set();
for (const {
path
}
of filesWithPaths) {
if (path) {
const parts = path.split('/').filter(Boolean);
let currentPath = '';
for (const part of parts) {
currentPath += part + '/';
folderPaths.add(currentPath);
}
}
}
for (const folderPath of folderPaths) {
try {
await $wire.createFolder(folderPath.slice(0, -1));
} catch (error) {
console.warn(`Folder ${folderPath} already exists or failed to create.`);
}
}
for (const f of filesWithPaths) {
this.uploadQueue.push({
file: f.file,
name: f.file.name,
path: f.path,
size: f.file.size,
progress: 0,
speed: 0,
uploadedBytes: 0,
status: 'pending',
error: null
});
}
const maxConcurrent = 3;
let activeUploads = [];
let completedCount = 0;
for (let i = 0; i < this.uploadQueue.length; i++) {
const uploadPromise = this.uploadFile(i)
.then(() => {
completedCount++;
this.currentFileIndex = completedCount;
const item = this.uploadQueue[i];
const relativePath = (item.path ? item.path.replace(/^\/+/, '') + '/' : '') + item.name;
uploadedFiles.push(relativePath);
})
.catch(() => {
completedCount++;
this.currentFileIndex = completedCount;
});
activeUploads.push(uploadPromise);
if (activeUploads.length >= maxConcurrent) {
await Promise.race(activeUploads);
activeUploads = activeUploads.filter(p => p.status !== 'fulfilled' && p.status !== 'rejected');
}
}
await Promise.allSettled(activeUploads);
const failed = this.uploadQueue.filter(f => f.status === 'error');
await $wire.$refresh();
if (failed.length === 0) {
new window.FilamentNotification()
.title('{{ trans('server/file.actions.upload.success') }}')
.success()
.send();
} else if (failed.length === this.totalFiles) {
new window.FilamentNotification()
.title('{{ trans('server/file.actions.upload.failed') }}')
.danger()
.send();
} else {
new window.FilamentNotification()
.title('{{ trans('server/file.actions.upload.failed') }}')
.danger()
.send();
}
if (uploadedFiles.length > 0) {
this.$nextTick(() => {
if (typeof $wire !== 'undefined' && $wire && typeof $wire.call === 'function') {
$wire.call('logUploadedFiles', uploadedFiles);
} else if (typeof window.livewire !== 'undefined' && typeof window.livewire.call === 'function') {
window.livewire.call('logUploadedFiles', uploadedFiles);
} else if (typeof Livewire !== 'undefined' && typeof Livewire.call === 'function') {
Livewire.call('logUploadedFiles', uploadedFiles);
} else {
console.warn('Could not call Livewire method logUploadedFiles; Livewire not found.');
}
});
}
if (this.autoCloseTimer) clearTimeout(this.autoCloseTimer);
this.autoCloseTimer = setTimeout(() => {
this.isUploading = false;
this.uploadQueue = [];
}, 1000);
} catch (error) {
console.error('Upload error:', error);
new window.FilamentNotification()
.title('{{ trans('server/file.actions.upload.error') }}')
.danger()
.send();
this.isUploading = false;
}
},
async uploadFile(index) {
const fileData = this.uploadQueue[index];
fileData.status = 'uploading';
try {
const uploadUrl = await $wire.getUploadUrl();
const url = new URL(uploadUrl);
let basePath = @js($this->path);
if (fileData.path && fileData.path.trim() !== '') {
basePath = basePath.replace(/\/+$/, '') + '/' + fileData.path.replace(/^\/+/, '');
}
url.searchParams.append('directory', basePath);
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('files', fileData.file);
let lastLoaded = 0;
let lastTime = Date.now();
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
fileData.uploadedBytes = e.loaded;
fileData.progress = Math.round((e.loaded / e.total) * 100);
const now = Date.now();
const timeDiff = (now - lastTime) / 1000;
if (timeDiff > 0.1) {
const bytesDiff = e.loaded - lastLoaded;
fileData.speed = bytesDiff / timeDiff;
lastTime = now;
lastLoaded = e.loaded;
}
}
});
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
fileData.status = 'complete';
fileData.progress = 100;
resolve();
} else {
fileData.status = 'error';
fileData.error = `Upload failed (${xhr.status})`;
reject(new Error(fileData.error));
}
};
xhr.onerror = () => {
fileData.status = 'error';
fileData.error = 'Network error';
reject(new Error('Network error'));
};
xhr.open('POST', url.toString());
xhr.send(formData);
});
} catch (err) {
fileData.status = 'error';
fileData.error = 'Failed to get upload token';
throw err;
}
},
formatBytes(bytes) {
if (bytes === 0) return '0.00 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
},
formatSpeed(bytesPerSecond) {
return this.formatBytes(bytesPerSecond) + '/s';
},
handleEscapeKey(e) {
if (e.key === 'Escape' && this.isUploading) {
this.isUploading = false;
this.uploadQueue = [];
}
},
}"
@dragenter.window="handleDragEnter($event)"
@dragleave.window="handleDragLeave($event)"
@dragover.window="handleDragOver($event)"
@drop.window="handleDrop($event)"
@keydown.window="handleEscapeKey($event)"
class="relative"
>
<div
x-show="isDragging"
x-cloak
x-transition:enter="transition-[opacity] duration-200 ease-out"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition-[opacity] duration-150 ease-in"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 z-50 flex items-center justify-center bg-gray-900/50 dark:bg-gray-100/20"
>
<div class="rounded-lg bg-white p-8 shadow-xl dark:bg-gray-800">
<div class="flex flex-col items-center gap-4">
<svg xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icons-tabler-outline icon-tabler-upload size-12 text-success-500"
viewBox="0 0 36 36" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2" />
<path d="M7 9l5 -5l5 5" />
<path d="M12 4l0 12" />
</svg>
<p class="text-lg font-semibold text-gray-900 dark:text-gray-100">
{{ trans('server/file.actions.upload.drop_files') }}
</p>
</div>
</div>
</div>
<div
x-show="isUploading"
x-cloak
class="fixed inset-0 z-50 flex items-center justify-center bg-gray-900/50 dark:bg-gray-100/20 p-4"
>
<div
class="rounded-lg bg-white shadow-xl dark:bg-gray-800 max-w-1/2 max-h-[50vh] overflow-hidden flex flex-col">
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-center">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
{{ trans('server/file.actions.upload.header') }} -
<span class="text-lg text-gray-600 dark:text-gray-400">
<span x-text="currentFileIndex"></span> of <span x-text="totalFiles"></span>
</span>
</h3>
</div>
<div class="flex-1 overflow-y-auto">
<div class="overflow-hidden">
<table class="w-full divide-y divide-gray-200 dark:divide-white/5">
<tbody class="divide-y divide-gray-200 dark:divide-white/5 bg-white dark:bg-gray-900">
<template x-for="(fileData, index) in uploadQueue" :key="index">
<tr class="transition duration-75 hover:bg-gray-50 dark:hover:bg-white/5">
<td class="px-4 py-4 sm:px-6">
<div class="flex flex-col gap-y-1">
<div
class="text-sm font-medium leading-6 text-gray-950 dark:text-white truncate max-w-xs"
x-text="(fileData.path ? fileData.path + '/' : '') + fileData.name">
</div>
<div x-show="fileData.status === 'error'"
class="text-xs text-danger-600 dark:text-danger-400"
x-text="fileData.error"></div>
</div>
</td>
<td class="px-4 py-4 sm:px-6">
<div class="text-sm text-gray-500 dark:text-gray-400"
x-text="formatBytes(fileData.size)"></div>
</td>
<td class="px-4 py-4 sm:px-6">
<div x-show="fileData.status === 'uploading' || fileData.status === 'complete'"
class="flex justify-between items-center text-sm">
<span class="font-medium text-gray-700 dark:text-gray-300"
x-text="`${fileData.progress}%`"></span>
<span x-show="fileData.status === 'uploading' && fileData.speed > 0"
class="text-gray-500 dark:text-gray-400"
x-text="formatSpeed(fileData.speed)"></span>
</div>
<span x-show="fileData.status === 'pending'"
class="text-sm text-gray-500 dark:text-gray-400">
</span>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
</div>
{{ $this->table }}
</div>
<x-filament-actions::modals />
</x-filament-panels::page>

277
yarn.lock
View File

@ -182,105 +182,115 @@
"@emnapi/runtime" "^1.4.0"
"@tybys/wasm-util" "^0.9.0"
"@rollup/rollup-android-arm-eabi@4.40.0":
version "4.40.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz#d964ee8ce4d18acf9358f96adc408689b6e27fe3"
integrity sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==
"@rollup/rollup-android-arm-eabi@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.1.tgz#63f6bdc496180079976e655473d5bea99b21f3ff"
integrity sha512-bxZtughE4VNVJlL1RdoSE545kc4JxL7op57KKoi59/gwuU5rV6jLWFXXc8jwgFoT6vtj+ZjO+Z2C5nrY0Cl6wA==
"@rollup/rollup-android-arm64@4.40.0":
version "4.40.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz#9b5e130ecc32a5fc1e96c09ff371743ee71a62d3"
integrity sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==
"@rollup/rollup-android-arm64@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.1.tgz#177f5e504d2f332edd0ddd3682f91ab72528fb60"
integrity sha512-44a1hreb02cAAfAKmZfXVercPFaDjqXCK+iKeVOlJ9ltvnO6QqsBHgKVPTu+MJHSLLeMEUbeG2qiDYgbFPU48g==
"@rollup/rollup-darwin-arm64@4.40.0":
version "4.40.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz#ef439182c739b20b3c4398cfc03e3c1249ac8903"
integrity sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==
"@rollup/rollup-darwin-arm64@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.1.tgz#ffdbe0cc43c88a35be2821f99cdff4c7a5ee2116"
integrity sha512-usmzIgD0rf1syoOZ2WZvy8YpXK5G1V3btm3QZddoGSa6mOgfXWkkv+642bfUUldomgrbiLQGrPryb7DXLovPWQ==
"@rollup/rollup-darwin-x64@4.40.0":
version "4.40.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz#d7380c1531ab0420ca3be16f17018ef72dd3d504"
integrity sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==
"@rollup/rollup-darwin-x64@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.1.tgz#27a4852923010abbcd1f028c7e8bd6bf0ccbe755"
integrity sha512-is3r/k4vig2Gt8mKtTlzzyaSQ+hd87kDxiN3uDSDwggJLUV56Umli6OoL+/YZa/KvtdrdyNfMKHzL/P4siOOmg==
"@rollup/rollup-freebsd-arm64@4.40.0":
version "4.40.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz#cbcbd7248823c6b430ce543c59906dd3c6df0936"
integrity sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==
"@rollup/rollup-freebsd-arm64@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.1.tgz#a02b83018e487674ab445198786bef9b41cad9f0"
integrity sha512-QJ1ksgp/bDJkZB4daldVmHaEQkG4r8PUXitCOC2WRmRaSaHx5RwPoI3DHVfXKwDkB+Sk6auFI/+JHacTekPRSw==
"@rollup/rollup-freebsd-x64@4.40.0":
version "4.40.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz#96bf6ff875bab5219c3472c95fa6eb992586a93b"
integrity sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==
"@rollup/rollup-freebsd-x64@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.1.tgz#fe898a4f0ff7c30f8377c3976ae76b89720c41da"
integrity sha512-J6ma5xgAzvqsnU6a0+jgGX/gvoGokqpkx6zY4cWizRrm0ffhHDpJKQgC8dtDb3+MqfZDIqs64REbfHDMzxLMqQ==
"@rollup/rollup-linux-arm-gnueabihf@4.40.0":
version "4.40.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz#d80cd62ce6d40f8e611008d8dbf03b5e6bbf009c"
integrity sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==
"@rollup/rollup-linux-arm-gnueabihf@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.1.tgz#be5a731a9f7bd7bc707457a768940b6107a9215e"
integrity sha512-JzWRR41o2U3/KMNKRuZNsDUAcAVUYhsPuMlx5RUldw0E4lvSIXFUwejtYz1HJXohUmqs/M6BBJAUBzKXZVddbg==
"@rollup/rollup-linux-arm-musleabihf@4.40.0":
version "4.40.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz#75440cfc1e8d0f87a239b4c31dfeaf4719b656b7"
integrity sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==
"@rollup/rollup-linux-arm-musleabihf@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.1.tgz#30ce6548a9e3591303507c37280300edb0cd1d14"
integrity sha512-L8kRIrnfMrEoHLHtHn+4uYA52fiLDEDyezgxZtGUTiII/yb04Krq+vk3P2Try+Vya9LeCE9ZHU8CXD6J9EhzHQ==
"@rollup/rollup-linux-arm64-gnu@4.40.0":
version "4.40.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz#ac527485ecbb619247fb08253ec8c551a0712e7c"
integrity sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==
"@rollup/rollup-linux-arm64-gnu@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.1.tgz#ec76f4223335e86cd61b0d596f34e97223f4f711"
integrity sha512-ysAc0MFRV+WtQ8li8hi3EoFi7us6d1UzaS/+Dp7FYZfg3NdDljGMoVyiIp6Ucz7uhlYDBZ/zt6XI0YEZbUO11Q==
"@rollup/rollup-linux-arm64-musl@4.40.0":
version "4.40.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz#74d2b5cb11cf714cd7d1682e7c8b39140e908552"
integrity sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==
"@rollup/rollup-linux-arm64-musl@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.1.tgz#9d4d87c2988ec8e4bb3cf4516dda7ef6d09dcd3d"
integrity sha512-UV6l9MJpDbDZZ/fJvqNcvO1PcivGEf1AvKuTcHoLjVZVFeAMygnamCTDikCVMRnA+qJe+B3pSbgX2+lBMqgBhA==
"@rollup/rollup-linux-loongarch64-gnu@4.40.0":
version "4.40.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz#a0a310e51da0b5fea0e944b0abd4be899819aef6"
integrity sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==
"@rollup/rollup-linux-loong64-gnu@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.1.tgz#584bc6f3c33b30c3dbfdad36ac9c7792e4df5199"
integrity sha512-UDUtelEprkA85g95Q+nj3Xf0M4hHa4DiJ+3P3h4BuGliY4NReYYqwlc0Y8ICLjN4+uIgCEvaygYlpf0hUj90Yg==
"@rollup/rollup-linux-powerpc64le-gnu@4.40.0":
version "4.40.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz#4077e2862b0ac9f61916d6b474d988171bd43b83"
integrity sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==
"@rollup/rollup-linux-ppc64-gnu@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.1.tgz#3e9a3b095a7d7da6043cb9caa54439d3b598aaf5"
integrity sha512-vrRn+BYhEtNOte/zbc2wAUQReJXxEx2URfTol6OEfY2zFEUK92pkFBSXRylDM7aHi+YqEPJt9/ABYzmcrS4SgQ==
"@rollup/rollup-linux-riscv64-gnu@4.40.0":
version "4.40.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz#5812a1a7a2f9581cbe12597307cc7ba3321cf2f3"
integrity sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==
"@rollup/rollup-linux-riscv64-gnu@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.1.tgz#f3c3d6523d246eef4aa1eed265f1ba31b9eef7c8"
integrity sha512-gto/1CxHyi4A7YqZZNznQYrVlPSaodOBPKM+6xcDSCMVZN/Fzb4K+AIkNz/1yAYz9h3Ng+e2fY9H6bgawVq17w==
"@rollup/rollup-linux-riscv64-musl@4.40.0":
version "4.40.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz#973aaaf4adef4531375c36616de4e01647f90039"
integrity sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==
"@rollup/rollup-linux-riscv64-musl@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.1.tgz#0a8944b4f29a1ba923fb9c2ddb829e621f004988"
integrity sha512-KZ6Vx7jAw3aLNjFR8eYVcQVdFa/cvBzDNRFM3z7XhNNunWjA03eUrEwJYPk0G8V7Gs08IThFKcAPS4WY/ybIrQ==
"@rollup/rollup-linux-s390x-gnu@4.40.0":
version "4.40.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz#9bad59e907ba5bfcf3e9dbd0247dfe583112f70b"
integrity sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==
"@rollup/rollup-linux-s390x-gnu@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.1.tgz#bcb48f2d509ef6b33ba89f7d76a2f3805be8d4c8"
integrity sha512-HvEixy2s/rWNgpwyKpXJcHmE7om1M89hxBTBi9Fs6zVuLU4gOrEMQNbNsN/tBVIMbLyysz/iwNiGtMOpLAOlvA==
"@rollup/rollup-linux-x64-gnu@4.40.0":
version "4.40.0"
resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz"
integrity sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==
"@rollup/rollup-linux-x64-gnu@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.1.tgz#ca9045e3b8e8dc0797e55d0229d5c664211bf366"
integrity sha512-E/n8x2MSjAQgjj9IixO4UeEUeqXLtiA7pyoXCFYLuXpBA/t2hnbIdxHfA7kK9BFsYAoNU4st1rHYdldl8dTqGA==
"@rollup/rollup-linux-x64-musl@4.40.0":
version "4.40.0"
resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz"
integrity sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==
"@rollup/rollup-linux-x64-musl@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.1.tgz#740876db76078e37bd43cc8584ff1c7f6b382df8"
integrity sha512-IhJ087PbLOQXCN6Ui/3FUkI9pWNZe/Z7rEIVOzMsOs1/HSAECCvSZ7PkIbkNqL/AZn6WbZvnoVZw/qwqYMo4/w==
"@rollup/rollup-win32-arm64-msvc@4.40.0":
version "4.40.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz#c5bee19fa670ff5da5f066be6a58b4568e9c650b"
integrity sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==
"@rollup/rollup-openharmony-arm64@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.1.tgz#3ff19213afe46b806fb6ec105f2664e4027e4cbc"
integrity sha512-0++oPNgLJHBblreu0SFM7b3mAsBJBTY0Ksrmu9N6ZVrPiTkRgda52mWR7TKhHAsUb9noCjFvAw9l6ZO1yzaVbA==
"@rollup/rollup-win32-ia32-msvc@4.40.0":
version "4.40.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz#846e02c17044bd922f6f483a3b4d36aac6e2b921"
integrity sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==
"@rollup/rollup-win32-arm64-msvc@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.1.tgz#cbba39610831747f8050a306811776534df1030d"
integrity sha512-VJXivz61c5uVdbmitLkDlbcTk9Or43YC2QVLRkqp86QoeFSqI81bNgjhttqhKNMKnQMWnecOCm7lZz4s+WLGpQ==
"@rollup/rollup-win32-x64-msvc@4.40.0":
version "4.40.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz#fd92d31a2931483c25677b9c6698106490cbbc76"
integrity sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==
"@rollup/rollup-win32-ia32-msvc@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.1.tgz#5453c7ebba95d2bbfcc94c744c05586d587fb640"
integrity sha512-NmZPVTUOitCXUH6erJDzTQ/jotYw4CnkMDjCYRxNHVD9bNyfrGoIse684F9okwzKCV4AIHRbUkeTBc9F2OOH5Q==
"@rollup/rollup-win32-x64-gnu@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.1.tgz#01e1acb0dacb220d13c8992340f7bc868a564832"
integrity sha512-2SNj7COIdAf6yliSpLdLG8BEsp5lgzRehgfkP0Av8zKfQFKku6JcvbobvHASPJu4f3BFxej5g+HuQPvqPhHvpQ==
"@rollup/rollup-win32-x64-msvc@4.53.1":
version "4.53.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.1.tgz#56eeb602545ec03ce84633b331c2e3ece07b99c3"
integrity sha512-rLarc1Ofcs3DHtgSzFO31pZsCh8g05R2azN1q3fF+H423Co87My0R+tazOEvYVKXSLh8C4LerMK41/K7wlklcg==
"@tailwindcss/forms@^0.5.9":
version "0.5.10"
@ -410,10 +420,10 @@
dependencies:
tslib "^2.4.0"
"@types/estree@1.0.7":
version "1.0.7"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.7.tgz#4158d3105276773d5b7695cd4834b1722e4f37a8"
integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==
"@types/estree@1.0.8":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
"@xterm/addon-fit@^0.10.0":
version "0.10.0"
@ -632,6 +642,11 @@ escalade@^3.1.1, escalade@^3.2.0:
resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz"
integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
fdir@^6.5.0:
version "6.5.0"
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350"
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
foreground-child@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f"
@ -817,9 +832,9 @@ minipass@^7.1.2:
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
nanoid@^3.3.8:
nanoid@^3.3.11:
version "3.3.11"
resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
node-releases@^2.0.19:
@ -860,6 +875,11 @@ picomatch@^2.3.1:
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
picomatch@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042"
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
postcss-selector-parser@6.0.10:
version "6.0.10"
resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz"
@ -873,12 +893,12 @@ postcss-value-parser@^4.2.0:
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.5.3:
version "8.5.3"
resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz"
integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==
postcss@^8.5.6:
version "8.5.6"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c"
integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
dependencies:
nanoid "^3.3.8"
nanoid "^3.3.11"
picocolors "^1.1.1"
source-map-js "^1.2.1"
@ -897,33 +917,35 @@ require-directory@^2.1.1:
resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz"
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
rollup@^4.30.1:
version "4.40.0"
resolved "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz"
integrity sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==
rollup@^4.43.0:
version "4.53.1"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.53.1.tgz#84d1d378584a15dedfcdcff7767a8b9d92d8d3d9"
integrity sha512-n2I0V0lN3E9cxxMqBCT3opWOiQBzRN7UG60z/WDKqdX2zHUS/39lezBcsckZFsV6fUTSnfqI7kHf60jDAPGKug==
dependencies:
"@types/estree" "1.0.7"
"@types/estree" "1.0.8"
optionalDependencies:
"@rollup/rollup-android-arm-eabi" "4.40.0"
"@rollup/rollup-android-arm64" "4.40.0"
"@rollup/rollup-darwin-arm64" "4.40.0"
"@rollup/rollup-darwin-x64" "4.40.0"
"@rollup/rollup-freebsd-arm64" "4.40.0"
"@rollup/rollup-freebsd-x64" "4.40.0"
"@rollup/rollup-linux-arm-gnueabihf" "4.40.0"
"@rollup/rollup-linux-arm-musleabihf" "4.40.0"
"@rollup/rollup-linux-arm64-gnu" "4.40.0"
"@rollup/rollup-linux-arm64-musl" "4.40.0"
"@rollup/rollup-linux-loongarch64-gnu" "4.40.0"
"@rollup/rollup-linux-powerpc64le-gnu" "4.40.0"
"@rollup/rollup-linux-riscv64-gnu" "4.40.0"
"@rollup/rollup-linux-riscv64-musl" "4.40.0"
"@rollup/rollup-linux-s390x-gnu" "4.40.0"
"@rollup/rollup-linux-x64-gnu" "4.40.0"
"@rollup/rollup-linux-x64-musl" "4.40.0"
"@rollup/rollup-win32-arm64-msvc" "4.40.0"
"@rollup/rollup-win32-ia32-msvc" "4.40.0"
"@rollup/rollup-win32-x64-msvc" "4.40.0"
"@rollup/rollup-android-arm-eabi" "4.53.1"
"@rollup/rollup-android-arm64" "4.53.1"
"@rollup/rollup-darwin-arm64" "4.53.1"
"@rollup/rollup-darwin-x64" "4.53.1"
"@rollup/rollup-freebsd-arm64" "4.53.1"
"@rollup/rollup-freebsd-x64" "4.53.1"
"@rollup/rollup-linux-arm-gnueabihf" "4.53.1"
"@rollup/rollup-linux-arm-musleabihf" "4.53.1"
"@rollup/rollup-linux-arm64-gnu" "4.53.1"
"@rollup/rollup-linux-arm64-musl" "4.53.1"
"@rollup/rollup-linux-loong64-gnu" "4.53.1"
"@rollup/rollup-linux-ppc64-gnu" "4.53.1"
"@rollup/rollup-linux-riscv64-gnu" "4.53.1"
"@rollup/rollup-linux-riscv64-musl" "4.53.1"
"@rollup/rollup-linux-s390x-gnu" "4.53.1"
"@rollup/rollup-linux-x64-gnu" "4.53.1"
"@rollup/rollup-linux-x64-musl" "4.53.1"
"@rollup/rollup-openharmony-arm64" "4.53.1"
"@rollup/rollup-win32-arm64-msvc" "4.53.1"
"@rollup/rollup-win32-ia32-msvc" "4.53.1"
"@rollup/rollup-win32-x64-gnu" "4.53.1"
"@rollup/rollup-win32-x64-msvc" "4.53.1"
fsevents "~2.3.2"
rxjs-compat@^6.5.4:
@ -1037,6 +1059,14 @@ tapable@^2.2.0:
resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz"
integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
tinyglobby@^0.2.15:
version "0.2.15"
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2"
integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==
dependencies:
fdir "^6.5.0"
picomatch "^4.0.3"
tree-kill@^1.2.2:
version "1.2.2"
resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz"
@ -1068,14 +1098,17 @@ vite-plugin-full-reload@^1.1.0:
picocolors "^1.0.0"
picomatch "^2.3.1"
vite@6.2.6:
version "6.2.6"
resolved "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz"
integrity sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==
vite@7.1.11:
version "7.1.11"
resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.11.tgz#4d006746112fee056df64985191e846ebfb6007e"
integrity sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==
dependencies:
esbuild "^0.25.0"
postcss "^8.5.3"
rollup "^4.30.1"
fdir "^6.5.0"
picomatch "^4.0.3"
postcss "^8.5.6"
rollup "^4.43.0"
tinyglobby "^0.2.15"
optionalDependencies:
fsevents "~2.3.3"