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:
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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 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"
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user