From 71f3abe464509a7da3530935ea38d032af200f77 Mon Sep 17 00:00:00 2001 From: Boy132 Date: Sun, 26 Jan 2025 14:29:53 +0100 Subject: [PATCH] File manager improvements (#936) * add separate button for "save & close" * make language selection for editor work * fix download url * add info banner for .pelicanignore files * small cleanup * fix import * Move File Lang * add `ctrl+shift+s` for save & close * fix keybind * cleanup and fix default value for edit * remove unnecessary File::get & trait * More EditorLanguages not matching their names * mdx has its own highlighter --------- Co-authored-by: notCharles Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com> --- app/Enums/EditorLanguages.php | 45 +++++++++++- .../Server/Resources/FileResource.php | 1 + .../FileResource/Pages/DownloadFiles.php | 64 +++++++++++++++++ .../FileResource/Pages/EditFiles.php | 70 ++++++++++++------- .../FileResource/Pages/ListFiles.php | 36 +++------- config/filament-monaco-editor.php | 2 +- .../filament/plugins/monaco-editor.blade.php | 10 ++- .../filament/server/pages/edit-file.blade.php | 2 - 8 files changed, 173 insertions(+), 57 deletions(-) create mode 100644 app/Filament/Server/Resources/FileResource/Pages/DownloadFiles.php diff --git a/app/Enums/EditorLanguages.php b/app/Enums/EditorLanguages.php index 91dcaebe7..7480a412c 100644 --- a/app/Enums/EditorLanguages.php +++ b/app/Enums/EditorLanguages.php @@ -36,6 +36,7 @@ enum EditorLanguages: string implements HasLabel case java = 'java'; case javascript = 'javascript'; case julia = 'julia'; + case json = 'json'; case kotlin = 'kotlin'; case less = 'less'; case lexon = 'lexon'; @@ -89,7 +90,49 @@ enum EditorLanguages: string implements HasLabel case wgsl = 'wgsl'; case xml = 'xml'; case yaml = 'yaml'; - case json = 'json'; + + public static function fromWithAlias(string $match): self + { + return match ($match) { + 'h' => self::c, + + 'cc', 'hpp' => self::cpp, + + 'cs' => self::csharp, + + 'class' => self::java, + + 'htm' => self::html, + + 'js', 'mjs', 'cjs' => self::javascript, + + 'kt', 'kts' => self::kotlin, + + 'md' => self::markdown, + + 'm' => self::objectivec, + + 'pl', 'pm' => self::perl, + + 'php3', 'php4', 'php5', 'phtml' => self::php, + + 'py', 'pyc', 'pyo', 'pyi' => self::python, + + 'rdata', 'rds' => self::r, + + 'rb', 'erb' => self::ruby, + + 'sc' => self::scala, + + 'sh', 'zsh' => self::shell, + + 'ts', 'tsx' => self::typescript, + + 'yml' => self::yaml, + + default => self::tryFrom($match) ?? self::plaintext, + }; + } public function getLabel(): string { diff --git a/app/Filament/Server/Resources/FileResource.php b/app/Filament/Server/Resources/FileResource.php index fd956e9c2..d94da981d 100644 --- a/app/Filament/Server/Resources/FileResource.php +++ b/app/Filament/Server/Resources/FileResource.php @@ -56,6 +56,7 @@ class FileResource extends Resource return [ 'edit' => Pages\EditFiles::route('/edit/{path}'), 'search' => Pages\SearchFiles::route('/search/{searchTerm}'), // TODO: find better way? + 'download' => Pages\DownloadFiles::route('/download/{path}'), 'index' => Pages\ListFiles::route('/{path?}'), ]; } diff --git a/app/Filament/Server/Resources/FileResource/Pages/DownloadFiles.php b/app/Filament/Server/Resources/FileResource/Pages/DownloadFiles.php new file mode 100644 index 000000000..2f147f68a --- /dev/null +++ b/app/Filament/Server/Resources/FileResource/Pages/DownloadFiles.php @@ -0,0 +1,64 @@ +authorizeAccess(); + + /** @var Server $server */ + $server = Filament::getTenant(); + + $token = $service + ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) + ->setUser(auth()->user()) + ->setClaims([ + 'file_path' => rawurldecode($path), + 'server_uuid' => $server->uuid, + ]) + ->handle($server->node, auth()->user()->id . $server->uuid); + + Activity::event('server:file.download') + ->property('file', $path) + ->log(); + + redirect()->away($server->node->getConnectionAddress() . '/download/file?token=' . $token->toString()); + } + + protected function authorizeAccess(): void + { + abort_unless(auth()->user()->can(Permission::ACTION_FILE_READ_CONTENT, Filament::getTenant()), 403); + } + + public static function route(string $path): PageRegistration + { + return new PageRegistration( + page: static::class, + route: fn (Panel $panel): Route => RouteFacade::get($path, static::class) + ->middleware(static::getRouteMiddleware($panel)) + ->withoutMiddleware(static::getWithoutRouteMiddleware($panel)) + ->where('path', '.*'), + ); + } +} diff --git a/app/Filament/Server/Resources/FileResource/Pages/EditFiles.php b/app/Filament/Server/Resources/FileResource/Pages/EditFiles.php index ef693d735..8fb29f775 100644 --- a/app/Filament/Server/Resources/FileResource/Pages/EditFiles.php +++ b/app/Filament/Server/Resources/FileResource/Pages/EditFiles.php @@ -6,7 +6,7 @@ use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor; use App\Enums\EditorLanguages; use App\Facades\Activity; use App\Filament\Server\Resources\FileResource; -use App\Models\File; +use App\Livewire\AlertBanner; use App\Models\Permission; use App\Models\Server; use App\Repositories\Daemon\DaemonFileRepository; @@ -18,7 +18,6 @@ use Filament\Forms\Concerns\InteractsWithForms; use Filament\Forms\Form; use Filament\Forms\Get; use Filament\Notifications\Notification; -use Filament\Pages\Concerns\HasUnsavedDataChangesAlert; use Filament\Pages\Concerns\InteractsWithFormActions; use Filament\Panel; use Filament\Resources\Pages\Page; @@ -34,7 +33,6 @@ use Livewire\Attributes\Locked; */ class EditFiles extends Page { - use HasUnsavedDataChangesAlert; use InteractsWithFormActions; use InteractsWithForms; @@ -54,27 +52,34 @@ class EditFiles extends Page /** @var Server $server */ $server = Filament::getTenant(); - File::get($server, dirname($this->path))->orderByDesc('is_directory')->orderBy('name'); - return $form ->schema([ - Select::make('lang') - ->live() - ->label('') - ->placeholder('File Language') - ->options(EditorLanguages::class) - ->hidden() //TODO Fix Dis - ->default(function () { - $ext = pathinfo($this->path, PATHINFO_EXTENSION); - - if ($ext === 'yml') { - return 'yaml'; - } - - return $ext; - }), Section::make('Editing: ' . $this->path) ->footerActions([ + Action::make('save_and_close') + ->label('Save & Close') + ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server)) + ->icon('tabler-device-floppy') + ->keyBindings('mod+shift+s') + ->action(function (DaemonFileRepository $fileRepository) use ($server) { + $data = $this->form->getState(); + + $fileRepository + ->setServer($server) + ->putContent($this->path, $data['editor'] ?? ''); + + Activity::event('server:file.write') + ->property('file', $this->path) + ->log(); + + Notification::make() + ->success() + ->title('File saved') + ->body(fn () => $this->path) + ->send(); + + $this->redirect(ListFiles::getUrl(['path' => dirname($this->path)])); + }), Action::make('save') ->label('Save') ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server)) @@ -93,12 +98,9 @@ class EditFiles extends Page Notification::make() ->success() - ->duration(5000) // 5 seconds - ->title('Saved File') + ->title('File saved') ->body(fn () => $this->path) ->send(); - - $this->redirect(ListFiles::getUrl(['path' => dirname($this->path)])); }), Action::make('cancel') ->label('Cancel') @@ -108,10 +110,17 @@ class EditFiles extends Page ]) ->footerActionsAlignment(Alignment::End) ->schema([ + Select::make('lang') + ->label('Syntax Highlighting') + ->live() + ->options(EditorLanguages::class) + ->selectablePlaceholder(false) + ->afterStateUpdated(fn ($state) => $this->dispatch('setLanguage', lang: $state)) + ->default(fn () => EditorLanguages::fromWithAlias(pathinfo($this->path, PATHINFO_EXTENSION))), MonacoEditor::make('editor') ->label('') ->placeholderText('') - ->formatStateUsing(function (DaemonFileRepository $fileRepository) use ($server) { + ->default(function (DaemonFileRepository $fileRepository) use ($server) { try { return $fileRepository ->setServer($server) @@ -120,7 +129,7 @@ class EditFiles extends Page abort(404, $this->path . ' not found.'); } }) - ->language(fn (Get $get) => $get('lang') ?? 'plaintext') + ->language(fn (Get $get) => $get('lang')) ->view('filament.plugins.monaco-editor'), ]), ]); @@ -133,6 +142,15 @@ class EditFiles extends Page $this->path = $path; $this->form->fill(); + + if (str($path)->endsWith('.pelicanignore')) { + AlertBanner::make() + ->title('You\'re editing a .pelicanignore file!') + ->body('Any files or directories listed in here will be excluded from backups. Wildcards are supported by using an asterisk (*).
You can negate a prior rule by prepending an exclamation point (!).') + ->info() + ->closable() + ->send(); + } } protected function authorizeAccess(): void diff --git a/app/Filament/Server/Resources/FileResource/Pages/ListFiles.php b/app/Filament/Server/Resources/FileResource/Pages/ListFiles.php index 6db4fc4e0..910633799 100644 --- a/app/Filament/Server/Resources/FileResource/Pages/ListFiles.php +++ b/app/Filament/Server/Resources/FileResource/Pages/ListFiles.php @@ -10,10 +10,8 @@ use App\Models\File; use App\Models\Permission; use App\Models\Server; use App\Repositories\Daemon\DaemonFileRepository; -use App\Services\Nodes\NodeJWTService; use App\Filament\Components\Tables\Columns\BytesColumn; use App\Filament\Components\Tables\Columns\DateTimeColumn; -use Carbon\CarbonImmutable; use Filament\Actions\Action as HeaderAction; use Filament\Facades\Filament; use Filament\Forms\Components\CheckboxList; @@ -21,6 +19,7 @@ use Filament\Forms\Components\FileUpload; use Filament\Forms\Components\Placeholder; use Filament\Forms\Components\Select; use Filament\Forms\Components\Tabs; +use Filament\Forms\Components\Tabs\Tab; use Filament\Forms\Components\TextInput; use Filament\Forms\Get; use Filament\Notifications\Notification; @@ -175,22 +174,7 @@ class ListFiles extends ListRecords ->label('Download') ->icon('tabler-download') ->visible(fn (File $file) => $file->is_file) - ->action(function (File $file, NodeJWTService $service) use ($server) { - $token = $service - ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) - ->setUser(auth()->user()) - ->setClaims([ - 'file_path' => rawurldecode(join_paths($this->path, $file->name)), - 'server_uuid' => $server->uuid, - ]) - ->handle($server->node, auth()->user()->id . $server->uuid); - - Activity::event('server:file.download') - ->property('file', join_paths($this->path, $file->name)) - ->log(); - - return redirect()->away(sprintf('%s/download/file?token=%s', $server->node->getConnectionAddress(), $token->toString())); // TODO: download works, but breaks modals - }), + ->url(fn () => DownloadFiles::getUrl(['path' => $this->path]), true), Action::make('move') ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server)) ->label('Move') @@ -448,15 +432,16 @@ class ListFiles extends ListRecords ->label('File Name') ->required(), Select::make('lang') + ->label('Syntax Highlighting') ->live() - ->hidden() //TODO: Make file language selection work - ->label('Language') - ->placeholder('File Language') - ->options(EditorLanguages::class), + ->options(EditorLanguages::class) + ->selectablePlaceholder(false) + ->afterStateUpdated(fn ($state) => $this->dispatch('setLanguage', lang: $state)) + ->default(EditorLanguages::plaintext->value), MonacoEditor::make('editor') ->label('') ->view('filament.plugins.monaco-editor') - ->language(fn (Get $get) => $get('lang')), + ->language(fn (Get $get) => $get('lang') ?? 'plaintext'), ]), HeaderAction::make('new_folder') ->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server)) @@ -504,13 +489,12 @@ class ListFiles extends ListRecords } return redirect(ListFiles::getUrl(['path' => $this->path])); - }) ->form([ Tabs::make() ->contained(false) ->schema([ - Tabs\Tab::make('Upload Files') + Tab::make('Upload Files') ->live() ->schema([ FileUpload::make('files') @@ -520,7 +504,7 @@ class ListFiles extends ListRecords ->preserveFilenames() ->multiple(), ]), - Tabs\Tab::make('Upload From URL') + Tab::make('Upload From URL') ->live() ->disabled(fn (Get $get) => count($get('files')) > 0) ->schema([ diff --git a/config/filament-monaco-editor.php b/config/filament-monaco-editor.php index 7f87c0969..2ced43128 100644 --- a/config/filament-monaco-editor.php +++ b/config/filament-monaco-editor.php @@ -6,7 +6,7 @@ return [ 'show-full-screen-toggle' => true, 'show-placeholder' => true, 'placeholder-text' => 'Your code here...', - 'show-loader' => true, + 'show-loader' => false, 'font-size' => '16px', 'line-numbers-min-chars' => true, 'automatic-layout' => true, diff --git a/resources/views/filament/plugins/monaco-editor.blade.php b/resources/views/filament/plugins/monaco-editor.blade.php index 3980c1bbb..78bfb23db 100644 --- a/resources/views/filament/plugins/monaco-editor.blade.php +++ b/resources/views/filament/plugins/monaco-editor.blade.php @@ -1,3 +1,11 @@ +@script + +@endscript +