add time_added field to FilesBlock; update file upload logic to set timestamp and improve file display in FileBlock with additional metadata

This commit is contained in:
F4ilji
2025-07-25 12:36:17 +05:00
parent e403203e72
commit 16e7b9fe46
10 changed files with 211 additions and 66 deletions
@@ -24,6 +24,7 @@ class FilesBlock implements BlockSchema
->schema([
Hidden::make('expansion')->required(),
Hidden::make('size')->required(),
Hidden::make('time_added')->required(),
TextInput::make('title')
->label('Название файла')
->placeholder('Введите название файла')
@@ -51,8 +52,10 @@ class FilesBlock implements BlockSchema
->directory('files')
->downloadable()
->afterStateUpdated(function ($set, $state) {
$set('title', $state?->getClientOriginalName());
$set('expansion', $state?->getClientOriginalExtension());
$set('size', ByteConverter::bytesToHuman($state?->getSize()));
$set('time_added', Carbon::now()->timestamp);
})
->visibility('public')
->preserveFilenames()
@@ -63,4 +66,4 @@ class FilesBlock implements BlockSchema
->grid(2),
];
}
}
}
@@ -28,13 +28,11 @@ class PersonBlock implements BlockSchema
FileUpload::make('photo')
->label('Фотография')
->image()
->helperText('Рекомендуемый формат: WebP')
->optimize('webp')
->resize(50)
->disk('public')
->directory('images')
->imageEditor()
->required()
->downloadable()
->openable(),
Repeater::make('info')
@@ -60,4 +58,4 @@ class PersonBlock implements BlockSchema
->itemLabel(fn (array $state): ?string => $state['column'] ?? null),
];
}
}
}
+123
View File
@@ -0,0 +1,123 @@
<?php
namespace App\Filament\Pages;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Forms\Components\Actions\Action;
use Illuminate\Support\Str;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
use Illuminate\Support\Facades\Storage;
class UploadToUrl extends Page
{
protected static ?string $title = 'Быстрая загрузка файла';
protected static ?string $navigationIcon = 'heroicon-o-document-text';
protected static string $view = 'filament.pages.upload-to-url';
public ?array $data = [];
public function mount(): void
{
$this->form->fill();
}
public function form(Form $form): Form
{
return $form
->schema([
Section::make('Загрузка файла')
->description('Загрузите один файл. После загрузки вы сможете скопировать его путь.')
->schema([
FileUpload::make('data.uploadedFile') // Bind directly to data.uploadedFile
->label('Файл для загрузки')
->required()
->acceptedFileTypes(['application/pdf', 'image/*', 'video/*', 'audio/*', 'text/plain', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'])
->maxSize(20000) // 20MB
->disk('public')
->directory('files')
->visibility('public')
->helperText('Загрузите файл. Максимальный размер 20MB.')
->afterStateUpdated(function ($state) {
$this->data['uploadedFile'] = $state; // Assign to data array
$this->save();
}),
TextInput::make('uploadedFileUrl')
->label('Путь к файлу')
->columnSpan('full')
->suffixAction(
Action::make('copy')
->icon('heroicon-s-clipboard-document-check')
->action(function ($livewire, $state, $record) {
$livewire->js(
'window.navigator.clipboard.writeText("'. url('/') . $state.'");
$tooltip("'.__('Copied to clipboard').'", { timeout: 1500 });'
);
})),
]),
])
->statePath('data');
}
public function save(): void
{
if (empty($this->data['uploadedFile'])) { // Check data property
Notification::make()
->title('Нет файла для загрузки')
->warning()
->send();
return;
}
try {
$originalFileNameWithExtension = $this->data['uploadedFile']->getClientOriginalName();
$originalFileName = pathinfo($originalFileNameWithExtension, PATHINFO_FILENAME);
$extension = $this->data['uploadedFile']->getClientOriginalExtension();
$sluggedFileName = Str::slug($originalFileName);
$uniqueFileName = $sluggedFileName . '-' . md5(uniqid(rand(), true)) . '.' . $extension;
$path = $this->data['uploadedFile']->storeAs('files', $uniqueFileName, 'public');
$this->data['uploadedFileUrl'] = Storage::url($path);
$this->uploadedFileUrl = $this->data['uploadedFileUrl']; // Assign to data array
Notification::make()
->title('Файл успешно загружен')
->body("Файл `{$originalFileNameWithExtension}` загружен. Путь: `{$this->data['uploadedFileUrl']}`")
->success()
->send();
$this->data['uploadedFile'] = null; // Clear the file upload field
$this->form->fill($this->data); // Force form re-render, passing current data
} catch (\Throwable $e) {
$originalFileNameWithExtension = $this->data['uploadedFile']->getClientOriginalName() ?? 'Unknown';
Notification::make()
->title('Ошибка при загрузке файла')
->body("Не удалось загрузить файл `{$originalFileNameWithExtension}`: " . $e->getMessage())
->danger()
->send();
report($e);
}
}
protected function getFormActions(): array
{
return [
// Action::make('save')
// ->label('Загрузить файл')
// ->submit('save')
// ->color('primary')
// ->icon('heroicon-o-cloud-arrow-up'),
];
}
}
@@ -242,4 +242,4 @@ class UploadSchedules extends Page
{
return __('Расписание и группы');
}
}
}
+3
View File
@@ -0,0 +1,3 @@
<svg width="18" height="23" viewBox="0 0 18 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0V23H18V0H0ZM0 0H18V23H0V0ZM6.3 7.07692V8.84615H9.9C10.971 8.84615 11.7 9.56269 11.7 10.6154C11.7 11.6681 10.971 12.3846 9.9 12.3846C8.829 12.3846 8.1 11.6681 8.1 10.6154H6.3V16.8077H8.1V13.6284C8.6346 13.938 9.234 14.1538 9.9 14.1538C11.889 14.1538 13.5 12.5704 13.5 10.6154C13.5 8.66038 11.889 7.07692 9.9 7.07692H6.3Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 452 B

@@ -1,7 +1,7 @@
<template>
<transition name="fade" mode="out-in">
<PageSkeleton v-if="loading" key="skeleton" />
<div class="space-y-6" v-else key="content">
<div class="space-y-4" v-else key="content">
<component
v-for="(block, index) in blocks"
:key="index"
@@ -87,4 +87,4 @@ export default {
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
</style>
</style>
@@ -1,66 +1,75 @@
<template>
<template v-for="file in block.data.file">
<div class="mb-4">
<a class="" :href="'/storage/'+ file.path" download type="button">
<div class="flex border rounded-lg px-4 py-2 items-center justify-between duration-300 hover:bg-gray-100">
<div class="flex items-center justify-between">
<div class="min-w-[30px] min-h-[30px] bg-[#303030] flex justify-center items-center rounded-md mr-2">
<BasicIcon :name="file.expansion" class="w-5 h-5 text-white flex-shrink-0" />
</div>
<div>{{ textLimit(file.title, 70) }}</div>
</div>
<span class="text-sm text-gray-400">{{ file.size }}</span>
</div>
</a>
</div>
</template>
<template v-for="file in block.data.file">
<div class="mb-2">
<a target="_blank" class="" :href="'/storage/' + file.path" type="button">
<div
class="flex border rounded-lg px-4 py-2 items-center justify-between duration-300 hover:bg-gray-100"
>
<div class="flex items-center justify-between">
<div class="min-w-[30px] min-h-[30px] bg-[#303030] flex justify-center items-center rounded-md mr-3">
<BasicIcon
:name="file.expansion"
class="w-5 h-5 text-white flex-shrink-0"
/>
</div>
<div class="flex flex-col">
<span class="text-sm font-medium">{{ file.title }}</span>
<div class="flex items-center gap-1">
<span v-if="file?.time_added" class="text-xs text-gray-400">Обновлено {{ new Date(file.time_added * 1000).toLocaleDateString() }},</span>
<span class="text-xs text-gray-400">{{ file.size }}</span>
</div>
</div>
</div>
</div>
</a>
</div>
</template>
</template>
<script>
import slugify from "slugify";
import BasicIcon from "@/componentss/ui/icons/BasicIcon.vue";
export default {
name: "FileBlock",
components: {BasicIcon },
data() {
return {
toggler: false,
domainPath: null,
}
},
methods: {
generateSlug: function (text) {
return slugify(text, {
lower: true,
strict: true,
locale: 'ru'
});
},
textLimit(text, symbols) {
if (text.length > symbols) {
let LimitedText;
LimitedText = text.substring(0, symbols);
return LimitedText + "...";
}
return text;
},
},
mounted() {
this.domainPath = window.location.origin;
},
props: {
block: {
type: Object,
},
},
}
name: "FileBlock",
components: { BasicIcon },
data() {
return {
toggler: false,
domainPath: null,
};
},
methods: {
generateSlug: function (text) {
return slugify(text, {
lower: true,
strict: true,
locale: "ru",
});
},
textLimit(text, symbols) {
if (text.length > symbols) {
let LimitedText;
LimitedText = text.substring(0, symbols);
return LimitedText + "...";
}
return text;
},
},
mounted() {
this.domainPath = window.location.origin;
},
props: {
block: {
type: Object,
},
},
};
</script>
<style>
</style>
<style scoped>
.mask-fade {
mask-image: linear-gradient(to right, black 90%, transparent 100%);
-webkit-mask-image: linear-gradient(to right, black 90%, transparent 100%);
}
</style>
@@ -1,7 +1,7 @@
<template>
<div>
<PageSkeleton v-if="loading" />
<div class="space-y-6" v-else>
<div class="space-y-4" v-else>
<component
v-for="(block, index) in blocks"
:key="index"
@@ -90,4 +90,4 @@ export default {
<style scoped>
</style>
</style>
@@ -0,0 +1,9 @@
<x-filament-panels::page>
<x-filament-panels::form wire:submit="save">
{{ $this->form }}
<x-filament-panels::form.actions
:actions="$this->getFormActions()"
/>
</x-filament-panels::form>
</x-filament-panels::page>
+1 -1
View File
@@ -62,4 +62,4 @@ Route::middleware('ensure.browser')->group(function () {
//Route::get('/vk-handle', [VkPostController::class, 'index']);
//Route::get('/vk-handle/get-auth-token', [VkAuthController::class, 'getToken']);
//Route::get('/vk-handle/wall', [VkPostController::class, 'wall']);
//Route::get('/vk-handle/wall', [VkPostController::class, 'wall']);