This commit is contained in:
Dan Harrin
2021-11-07 22:03:16 +00:00
parent 65530c757b
commit 86dc95d4e1
37 changed files with 33124 additions and 6474 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"tabWidth": 4,
"semi": false,
"singleQuote": true
}
+2 -1
View File
@@ -1,6 +1,7 @@
const esbuild = require('esbuild')
const shouldWatch = process.argv.includes('--watch')
require('esbuild').build({
esbuild.build({
define: {
'process.env.NODE_ENV': shouldWatch ? `'production'` : `'development'`,
},
+24
View File
@@ -0,0 +1,24 @@
const colors = require('tailwindcss/colors')
module.exports = {
mode: 'jit',
purge: [
'./resources/**/*.blade.php',
'../forms/resources/**/*.blade.php',
'../tables/resources/**/*.blade.php',
],
theme: {
extend: {
colors: {
danger: colors.rose,
primary: colors.blue,
success: colors.green,
warning: colors.yellow,
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
],
}
Generated
+414 -319
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/
/******/
/******/ })()
;
+16778 -20
View File
File diff suppressed because it is too large Load Diff
+9 -3
View File
@@ -1,15 +1,19 @@
{
"name": "filament",
"scripts": {
"build": "node bin/build",
"watch": "node bin/build --watch"
"build": "mix --production && node bin/build",
"watch": "mix watch && node bin/build --watch"
},
"devDependencies": {
"@alpinejs/trap": "^3.5.0",
"@babel/runtime": "^7.15.3",
"@github/file-attachment-element": "^2.0.1",
"@github/markdown-toolbar-element": "^1.5.1",
"@tailwindcss/forms": "^0.3.4",
"@tailwindcss/typography": "^0.4.1",
"alpinejs": "^3.5.0",
"dayjs": "^1.10.6",
"esbuild": "^0.12.16",
"esbuild": "^0.8.57",
"filepond": "^4.28.2",
"filepond-plugin-file-validate-size": "^2.2.4",
"filepond-plugin-file-validate-type": "^1.2.6",
@@ -19,8 +23,10 @@
"filepond-plugin-image-resize": "^2.0.10",
"filepond-plugin-image-transform": "^3.8.6",
"imask": "^6.1.0",
"laravel-mix": "^6.0.37",
"marked": "^3.0.0",
"mdhl": "^0.0.6",
"tailwindcss": "^2.2.19",
"trix": "^1.3.1"
}
}
+1
View File
@@ -0,0 +1 @@
/vendor
+35
View File
@@ -0,0 +1,35 @@
{
"name": "filament/filament",
"description": "Effortlessly build TALL-powered admin panels.",
"license": "MIT",
"homepage": "https://github.com/laravel-filament/filament",
"support": {
"issues": "https://github.com/laravel-filament/filament/issues",
"source": "https://github.com/laravel-filament/filament"
},
"require": {
"php": "^8.0",
"livewire/livewire": "^2.6",
"spatie/laravel-package-tools": "^1.9"
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"Filament\\": "src"
}
},
"extra": {
"laravel": {
"providers": [
"Filament\\FilamentServiceProvider"
]
}
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev",
"prefer-stable": true
}
+3859
View File
File diff suppressed because it is too large Load Diff
+122
View File
@@ -0,0 +1,122 @@
<?php
use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Illuminate\Auth\Middleware\Authenticate;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Session\Middleware\AuthenticateSession;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;
return [
/*
|--------------------------------------------------------------------------
| Filament Path
|--------------------------------------------------------------------------
|
| The default is `admin` but you can change it to whatever works best and
| doesn't conflict with the routing in your application.
|
*/
'path' => env('FILAMENT_PATH', 'admin'),
/*
|--------------------------------------------------------------------------
| Filament Domain
|--------------------------------------------------------------------------
|
| You may change the domain where Filament should be active. If the domain
| is empty, all domains will be valid.
|
*/
'domain' => env('FILAMENT_DOMAIN', null),
/*
|--------------------------------------------------------------------------
| Pages
|--------------------------------------------------------------------------
|
| This is the namespace and directory that Filament will automatically
| register pages from.
|
*/
'pages' => [
'namespace' => 'App\\Filament\\Pages',
'path' => app_path('Filament/Pages'),
],
/*
|--------------------------------------------------------------------------
| Resources
|--------------------------------------------------------------------------
|
| This is the namespace and directory that Filament will automatically
| register resources from.
|
*/
'resources' => [
'namespace' => 'App\\Filament\\Resources',
'path' => app_path('Filament/Resources'),
],
/*
|--------------------------------------------------------------------------
| Widgets
|--------------------------------------------------------------------------
|
| This is the namespace and directory that Filament will automatically
| register widgets from.
|
*/
'widgets' => [
'namespace' => 'App\\Filament\\Widgets',
'path' => app_path('Filament/Widgets'),
'default' => [
'account' => true,
'info' => true,
],
],
/*
|--------------------------------------------------------------------------
| Default Filesystem Disk
|--------------------------------------------------------------------------
|
| This is the storage disk Filament will use to put media. You may use any
| of the disks defined in the `config/filesystems.php`.
|
*/
'default_filesystem_disk' => env('FILAMENT_FILESYSTEM_DRIVER', 'public'),
/*
|--------------------------------------------------------------------------
| Middleware
|--------------------------------------------------------------------------
|
| You may customise the middleware stack that Filament uses to handle
| requests.
|
*/
'middleware' => [
Authenticate::class,
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
AuthenticateSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
DispatchServingFilamentEvent::class,
],
];
+4415
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+270
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4
View File
@@ -0,0 +1,4 @@
{
"/app.js": "/app.js?id=cd79c4ecdcb5bc7cfc5c",
"/app.css": "/app.css?id=10f812baf1e04ac549d4"
}
+3
View File
@@ -0,0 +1,3 @@
---
title: Installation
---
+5
View File
@@ -0,0 +1,5 @@
@import '../../../forms/dist/module.esm.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
+12
View File
@@ -0,0 +1,12 @@
import '../css/app.css'
import Alpine from 'alpinejs'
import FormsAlpinePlugin from '../../../forms/dist/module.esm'
import Trap from '@alpinejs/trap'
Alpine.plugin(FormsAlpinePlugin)
Alpine.plugin(Trap)
window.Alpine = Alpine
Alpine.start()
@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" dir="{{ trans('filament::layout.direction') === 'rtl' ? 'rtl' : 'ltr' }}" class="antialiased bg-gray-100 js-focus-visible">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ $title ? "{$title} - " : null }} {{ config('app.name') }}</title>
@livewireStyles
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&family=JetBrains+Mono&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="{{ route('filament.asset', [
'id' => \Filament\get_asset_id('app.css'),
'path' => 'app.css',
]) }}" />
@foreach (\Filament\Facades\Filament::getStyles() as $path)
<link rel="stylesheet" href="{{ $path }}" />
@endforeach
</head>
<body>
<div class="min-h-screen bg-white text-gray-800 font-sans antialiased grid grid-cols-[20rem,1fr]">
<aside class="bg-gray-100 grid grid-rows-[4rem,1fr,auto]">
<header class="border-b px-6 flex items-center">
<a class="text-xl font-bold" href="">
{{ config('app.name') }}
</a>
</header>
<nav class="py-6">
<ul class="space-y-6 px-6">
@foreach (\Filament\Facades\Filament::getNavigation() as $group => $items)
<li>
@if ($group)
<p class="font-bold uppercase text-gray-600 text-xs tracking-wider">
{{ $group }}
</p>
@endif
<ul @class([
'text-sm space-y-1 -mx-3',
'mt-2' => $group,
])>
@foreach ($items as $item)
<li>
<a
href="{{ $item->getUrl() }}"
@class([
'flex items-center gap-3 px-3 py-2 rounded-md font-medium',
'bg-primary-500 text-white' => $item->isActive(),
])
>
<x-dynamic-component :component="$item->getIcon()" class="h-5 w-5" />
<span>
{{ $item->getLabel() }}
</span>
</a>
</li>
@endforeach
</ul>
</li>
@if (! $loop->last)
<li>
<div class="border-t -mr-6"></div>
</li>
@endif
@endforeach
</ul>
</nav>
<footer class="border-t px-6 py-3 flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-gray-200"></div>
<div>
<p class="text-sm font-bold">
{{ auth()->user()->name }}
</p>
<p class="text-xs text-gray-500">
Sign out
</p>
</div>
</footer>
</aside>
<main class="grid grid-rows-[4rem,1fr] relative shadow-lg">
<header class="flex items-center border-b px-6">
<ul class="flex gap-4 items-center font-medium text-sm">
<li>
<a href="" id="">Articles</a>
</li>
<li class="h-6 border-r -skew-x-12"></li>
<li>
<a href="" id="">why-inertia-js-is-goat</a>
</li>
<li class="h-6 border-r -skew-x-12"></li>
<li>
<a class="text-gray-500" href="" id="">Edit</a>
</li>
</ul>
</header>
{{ $slot }}
</main>
</div>
@livewireScripts
<script>
window.filamentData = @json(\Filament\Facades\Filament::getScriptData());
</script>
<script src="{{ route('filament.asset', [
'id' => Filament\get_asset_id('app.js'),
'path' => 'app.js',
]) }}"></script>
@foreach (\Filament\Facades\Filament::getScripts() as $path)
<script src="{{ $path }}"></script>
@endforeach
</body>
</html>
@@ -0,0 +1,3 @@
<div>
Dashboard
</div>
+24
View File
@@ -0,0 +1,24 @@
<?php
use Filament\Facades\Filament;
use Filament\Http\Controllers\AssetController;
use Filament\Http\Livewire;
use Illuminate\Support\Facades\Route;
Route::domain(config('filament.domain'))
->middleware(config('filament.middleware'))
->name('filament.')
->prefix(config('filament.path'))
->group(function () {
Route::get('/', Livewire\Dashboard::class)->name('dashboard');
foreach (Filament::getResources() as $resource) {
$resource::registerRoutes();
}
foreach (Filament::getPages() as $page) {
$page::registerRoutes();
}
Route::get('/assets/{path}', AssetController::class)->name('asset');
});
@@ -0,0 +1,15 @@
<?php
namespace Filament\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Http\Request;
class ServingFilament
{
use Dispatchable;
public function __construct(
public Request $request,
) {}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace Filament\Facades;
use Filament\FilamentManager;
use Illuminate\Support\Facades\Facade;
/**
* @method static array getPages()
* @method static array getNavigation()
* @method static array getNavigationGroups()
* @method static array getNavigationItems()
* @method static array getResources()
* @method static array getScripts()
* @method static array getScriptData()
* @method static array getStyles()
* @method static array getWidgets()
* @method static void registerNavigationGroups(array $groups)
* @method static void registerNavigationItems(array $items)
* @method static void registerPages(array $pages)
* @method static void registerResources(array $resources)
* @method static void registerScripts(array $scripts)
* @method static void registerScriptData(array $data)
* @method static void registerStyles(array $styles)
* @method static void registerWidgets(array $widgets)
*
* @see \Filament\FilamentManager
*/
class Filament extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'filament';
}
}
+141
View File
@@ -0,0 +1,141 @@
<?php
namespace Filament;
use Illuminate\Support\Str;
class FilamentManager
{
protected array $navigationGroups = [];
protected array $navigationItems = [];
protected array $pages = [];
protected array $resources = [];
protected array $scripts = [];
protected array $scriptData = [];
protected array $styles = [];
protected array $widgets = [];
public function registerNavigationGroups(array $groups): void
{
$this->navigationGroups = array_merge($this->navigationGroups, $groups);
}
public function registerNavigationItems(array $items): void
{
$this->navigationItems = array_merge($this->navigationItems, $items);
}
public function registerPages(array $pages): void
{
$this->pages = array_merge($this->pages, $pages);
foreach ($pages as $page) {
$page::registerNavigationItems();
}
}
public function registerResources(array $resources): void
{
$this->resources = array_merge($this->resources, $resources);
foreach ($resources as $resource) {
$resource::registerNavigationItems();
}
}
public function registerScripts(array $scripts): void
{
$this->scripts = array_merge($this->scripts, $scripts);
}
public function registerScriptData(array $data): void
{
$this->scriptData = array_merge($this->scriptData, $data);
}
public function registerStyles(array $styles): void
{
$this->styles = array_merge($this->styles, $styles);
}
public function registerWidgets(array $widgets): void
{
$this->widgets = array_merge($this->widgets, $widgets);
}
public function getNavigation(): array
{
$groupedItems = collect($this->navigationItems)
->sortBy(fn (NavigationItem $item): int => $item->getSort())
->groupBy(fn (NavigationItem $item): ?string => $item->getGroup());
$sortedGroups = $groupedItems
->keys()
->sortBy(function (?string $group): int {
if (! $group) {
return -1;
}
$sort = array_search($group, $this->navigationGroups);
if ($sort === false) {
return count($this->navigationGroups);
}
return $sort;
});
return $sortedGroups
->mapWithKeys(function (?string $group) use ($groupedItems): array {
return [$group => $groupedItems->get($group)];
})
->toArray();
}
public function getNavigationGroups(): array
{
return $this->navigationGroups;
}
public function getNavigationItems(): array
{
return $this->navigationItems;
}
public function getPages(): array
{
return $this->pages;
}
public function getResources(): array
{
return $this->resources;
}
public function getScripts(): array
{
return $this->scripts;
}
public function getScriptData(): array
{
return $this->scriptData;
}
public function getStyles(): array
{
return $this->styles;
}
public function getWidgets(): array
{
return $this->widgets;
}
}
@@ -0,0 +1,173 @@
<?php
namespace Filament;
use Filament\Facades\Filament;
use Filament\Http\Livewire\Dashboard;
use Filament\Resources\Resource;
use Filament\Widgets\Widget;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Livewire\Component;
use Livewire\Livewire;
use ReflectionClass;
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;
use SplFileInfo;
class FilamentServiceProvider extends PackageServiceProvider
{
public function configurePackage(Package $package): void
{
$package
->name('filament')
->hasConfigFile()
->hasRoutes(['web'])
->hasTranslations()
->hasViews();
}
public function boot(): void
{
parent::boot();
$this->bootLivewireComponents();
}
public function register(): void
{
parent::register();
$this->app->singleton('filament', function (): FilamentManager {
return new FilamentManager;
});
$this->mergeConfigFrom(__DIR__ . '/../config/filament.php', 'filament');
$this->discoverPages();
$this->discoverResources();
$this->discoverWidgets();
Filament::registerPages([
Dashboard::class,
]);
}
protected function bootLivewireComponents(): void
{
$this->registerLivewireComponentDirectory(__DIR__ . '/Http/Livewire', 'Filament\\Http\\Livewire', 'filament.core.');
$this->registerLivewireComponentDirectory(__DIR__ . '/Resources', 'Filament\\Resources', 'filament.core.resources.');
$this->registerLivewireComponentDirectory(app_path('Filament'), 'App\\Filament', 'filament.');
}
protected function discoverPages(): void
{
$filesystem = new Filesystem();
$filesystem->ensureDirectoryExists(config('filament.pages.path'));
Filament::registerPages(collect($filesystem->allFiles(config('filament.pages.path')))
->map(function (SplFileInfo $file): string {
return (string) Str::of(config('filament.pages.namespace'))
->append('\\', $file->getRelativePathname())
->replace(['/', '.php'], ['\\', '']);
})
->filter(fn (string $class): bool => is_subclass_of($class, Page::class) && (! (new ReflectionClass($class))->isAbstract()))
->toArray());
}
protected function discoverResources(): void
{
$filesystem = new Filesystem();
$filesystem->ensureDirectoryExists(config('filament.resources.path'));
Filament::registerResources(collect($filesystem->allFiles(config('filament.resources.path')))
->map(function (SplFileInfo $file): string {
return (string) Str::of(config('filament.resources.namespace'))
->append('\\', $file->getRelativePathname())
->replace(['/', '.php'], ['\\', '']);
})
->filter(fn (string $class): bool => is_subclass_of($class, Resource::class) && (! (new ReflectionClass($class))->isAbstract()))
->toArray());
}
protected function discoverWidgets(): void
{
$filesystem = new Filesystem();
$filesystem->ensureDirectoryExists(config('filament.widgets.path'));
Filament::registerWidgets(collect($filesystem->allFiles(config('filament.widgets.path')))
->map(function (SplFileInfo $file): string {
return (string) Str::of(config('filament.widgets.namespace'))
->append('\\', $file->getRelativePathname())
->replace(['/', '.php'], ['\\', '']);
})
->filter(fn ($class): bool => is_subclass_of($class, Widget::class) && (! (new ReflectionClass($class))->isAbstract()))
->toArray());
}
protected function mergeConfig(array $original, array $merging): array
{
$array = array_merge($original, $merging);
foreach ($original as $key => $value) {
if (! is_array($value)) {
continue;
}
if (! Arr::exists($merging, $key)) {
continue;
}
if (is_numeric($key)) {
continue;
}
if ($key === 'middleware') {
continue;
}
$array[$key] = $this->mergeConfig($value, $merging[$key]);
}
return $array;
}
protected function mergeConfigFrom($path, $key): void
{
$config = $this->app['config']->get($key, []);
$this->app['config']->set($key, $this->mergeConfig(require $path, $config));
}
protected function registerLivewireComponentDirectory(string $directory, string $namespace, string $aliasPrefix = ''): void
{
$filesystem = new Filesystem();
if (! $filesystem->isDirectory($directory)) {
return;
}
collect($filesystem->allFiles($directory))
->map(function (SplFileInfo $file) use ($namespace): string {
return (string) Str::of($namespace)
->append('\\', $file->getRelativePathname())
->replace(['/', '.php'], ['\\', '']);
})
->filter(fn (string $class): bool => is_subclass_of($class, Component::class) && (! (new ReflectionClass($class))->isAbstract()))
->each(function (string $class) use ($namespace, $aliasPrefix): void {
$alias = Str::of($class)
->after($namespace . '\\')
->replace(['/', '\\'], '.')
->prepend($aliasPrefix)
->explode('.')
->map([Str::class, 'kebab'])
->implode('.');
Livewire::component($alias, $class);
});
}
}
@@ -0,0 +1,51 @@
<?php
namespace Filament\Http\Controllers;
class AssetController
{
public function __invoke($path)
{
switch ($path) {
case 'app.css':
return $this->pretendResponseIsFile(__DIR__ . '/../../../dist/app.css', 'text/css; charset=utf-8');
case 'app.css.map':
return $this->pretendResponseIsFile(__DIR__ . '/../../../dist/app.css.map', 'text/css; charset=utf-8');
case 'app.js':
return $this->pretendResponseIsFile(__DIR__ . '/../../../dist/app.js', 'application/javascript; charset=utf-8');
case 'app.js.map':
return $this->pretendResponseIsFile(__DIR__ . '/../../../dist/app.js.map', 'application/json; charset=utf-8');
}
abort(404);
}
protected function getHttpDate($timestamp)
{
return sprintf('%s GMT', gmdate('D, d M Y H:i:s', $timestamp));
}
protected function pretendResponseIsFile($path, $contentType)
{
abort_if(! file_exists($path), 404);
$cacheControl = 'public, max-age=31536000';
$expires = strtotime('+1 year');
$lastModified = filemtime($path);
if (@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'] ?? '') === $lastModified) {
return response()->noContent(304, [
'Expires' => $this->getHttpDate($expires),
'Cache-Control' => $cacheControl,
]);
}
return response()->file($path, [
'Content-Type' => $contentType,
'Expires' => $this->getHttpDate($expires),
'Cache-Control' => $cacheControl,
'Last-Modified' => $this->getHttpDate($lastModified),
]);
}
}
@@ -0,0 +1,16 @@
<?php
namespace Filament\Http\Livewire;
use Filament\Pages\Page;
class Dashboard extends Page
{
public static ?string $navigationIcon = 'heroicon-o-home';
public static ?int $navigationSort = -2;
public static ?string $routeName = 'filament.dashboard';
public static string $view = 'filament::dashboard';
}
@@ -0,0 +1,17 @@
<?php
namespace Filament\Http\Middleware;
use Closure;
use Filament\Events\ServingFilament;
use Illuminate\Http\Request;
class DispatchServingFilamentEvent
{
public function handle(Request $request, Closure $next)
{
ServingFilament::dispatch($request);
return $next($request);
}
}
+103
View File
@@ -0,0 +1,103 @@
<?php
namespace Filament;
use Closure;
class NavigationItem
{
protected ?string $group = null;
protected ?Closure $isActiveWhen = null;
protected string $icon;
protected string $label;
protected ?int $sort = null;
protected ?string $url = null;
public static function make(): static
{
return new static();
}
public function group(?string $group): static
{
$this->group = $group;
return $this;
}
public function icon(string $icon): static
{
$this->icon = $icon;
return $this;
}
public function isActiveWhen(Closure $callback): static
{
$this->isActiveWhen = $callback;
return $this;
}
public function label(string $label): static
{
$this->label = $label;
return $this;
}
public function sort(?int $sort): static
{
$this->sort = $sort;
return $this;
}
public function url(?string $url): static
{
$this->url = $url;
return $this;
}
public function getGroup(): ?string
{
return $this->group;
}
public function getIcon(): string
{
return $this->icon;
}
public function getLabel(): string
{
return $this->label;
}
public function getSort(): int
{
return $this->sort ?? -1;
}
public function getUrl(): ?string
{
return $this->url;
}
public function isActive(): bool
{
$callback = $this->isActiveWhen;
if ($callback === null) {
return false;
}
return app()->call($callback);
}
}
+130
View File
@@ -0,0 +1,130 @@
<?php
namespace Filament\Pages;
use Filament\Concerns\CanRegisterNavigationItems;
use Filament\Concerns\CanRegisterRoutes;
use Filament\Facades\Filament;
use Filament\NavigationItem;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Str;
use Livewire\Component;
class Page extends Component
{
public static string $layout = 'filament::components.layouts.app';
public static ?string $navigationGroup = null;
public static ?string $navigationIcon = null;
public static ?string $navigationLabel = null;
public static ?int $navigationSort = null;
public static ?string $routeName = null;
public static ?string $slug = null;
public static ?string $title = null;
public static string $view;
public static function registerNavigationItems(): void
{
Filament::registerNavigationItems([
NavigationItem::make()
->group(static::getNavigationGroup())
->icon(static::getNavigationIcon())
->isActiveWhen(fn () => request()->routeIs([
$routeName = static::getRouteName(),
"{$routeName}*",
]))
->label(static::getNavigationLabel())
->sort(static::getNavigationSort())
->url(static::getNavigationUrl()),
]);
}
public static function registerRoutes(): void
{
//
}
public function render(): View
{
return view(static::$view, $this->getViewData())
->layout(static::$layout, $this->getLayoutData());
}
protected static function getBreadcrumbs(): array
{
return [
];
}
protected static function getNavigationGroup(): ?string
{
return static::$navigationGroup;
}
protected static function getNavigationIcon(): string
{
return static::$navigationIcon ?? 'heroicon-o-document-text';
}
protected static function getNavigationLabel(): string
{
return static::$navigationLabel ?? static::getTitle();
}
protected static function getNavigationSort(): ?int
{
return static::$navigationSort;
}
protected static function getNavigationUrl(): string
{
return '';
}
protected static function getRouteName(): string
{
$slug = static::getSlug();
return static::$routeName ?? "filament.pages.{$slug}";
}
protected static function getSlug(): string
{
return static::$slug ?? (string) Str::kebab(static::getTitle());
}
protected static function getTitle(): string
{
return static::$title ?? (string) Str::of(class_basename(static::class))
->kebab()
->replace('-', ' ')
->title();
}
protected static function getUrl(): string
{
return route(static::getRouteName());
}
protected function getLayoutData(): array
{
return [
'title' => static::getTitle(),
];
}
protected function getViewData(): array
{
return [
'title' => static::getTitle(),
];
}
}
+112
View File
@@ -0,0 +1,112 @@
<?php
namespace Filament\Resources;
use Filament\Facades\Filament;
use Filament\NavigationItem;
use Illuminate\Support\Str;
class Resource
{
public static ?string $label = null;
public static ?string $model = null;
public static ?string $navigationGroup = null;
public static ?string $navigationIcon = null;
public static ?string $navigationLabel = null;
public static ?int $navigationSort = null;
public static ?string $pluralLabel = null;
public static ?string $routeName = null;
public static ?string $slug = null;
public static function registerNavigationItems(): void
{
Filament::registerNavigationItems([
NavigationItem::make()
->group(static::getNavigationGroup())
->icon(static::getNavigationIcon())
->isActiveWhen(fn () => request()->routeIs([
$routeName = static::getRouteName(),
"{$routeName}*",
]))
->label(static::getNavigationLabel())
->sort(static::getNavigationSort())
->url(static::getNavigationUrl()),
]);
}
public static function registerRoutes(): void
{
//
}
protected static function getLabel(): string
{
return static::$label ?? (string) Str::of(class_basename(static::getModel()))
->kebab()
->replace('-', ' ');
}
protected static function getModel(): string
{
return static::$model ?? (string) Str::of(class_basename(static::class))
->beforeLast('Resource')
->prepend('App\\Models\\');
}
protected static function getNavigationGroup(): ?string
{
return static::$navigationGroup;
}
protected static function getNavigationIcon(): string
{
return static::$navigationIcon ?? 'heroicon-o-collection';
}
protected static function getNavigationLabel(): string
{
return static::$navigationLabel ?? Str::title(static::getPluralLabel());
}
protected static function getNavigationSort(): ?int
{
return static::$navigationSort;
}
protected static function getNavigationUrl(): string
{
return '';
}
protected static function getPluralLabel(): string
{
return static::$pluralLabel ?? Str::plural(static::getLabel());
}
protected static function getRouteName(): string
{
$slug = static::getSlug();
return static::$routeName ?? "filament.resources.{$slug}";
}
protected static function getSlug(): string
{
return static::$slug ?? (string) Str::of(class_basename(static::getModel()))
->plural()
->kebab();
}
protected static function getUrl(): string
{
return route(static::getRouteName());
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace Filament;
use Illuminate\Support\Str;
if (! function_exists('Filament\get_asset_id')) {
function get_asset_id($path)
{
$manifestPath = __DIR__ . '/../dist/mix-manifest.json';
if (! file_exists($manifestPath)) {
return null;
}
$manifest = json_decode(file_get_contents($manifestPath), true);
if (! array_key_exists($path, $manifest)) {
return null;
}
$path = $manifest[$path];
if (! str_contains($path, 'id=')) {
return null;
}
return (string) Str::of($path)->after('id=');
}
}
+29
View File
@@ -0,0 +1,29 @@
const colors = require('tailwindcss/colors')
const defaultTheme = require('tailwindcss/defaultTheme')
module.exports = {
mode: 'jit',
purge: [
'./packages/admin/resources/**/*.blade.php',
'./packages/forms/resources/**/*.blade.php',
'./packages/tables/resources/**/*.blade.php',
],
theme: {
extend: {
colors: {
danger: colors.rose,
primary: colors.yellow,
success: colors.green,
warning: colors.amber,
},
fontFamily: {
mono: ['JetBrains Mono', ...defaultTheme.fontFamily.mono],
sans: ['DM Sans', ...defaultTheme.fontFamily.sans],
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
],
}
+137 -137
View File
@@ -1,5 +1,137 @@
@charset "UTF-8";
/* node_modules/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css */
.filepond--image-preview-markup {
position: absolute;
left: 0;
top: 0;
}
.filepond--image-preview-wrapper {
z-index: 2;
}
.filepond--image-preview-overlay {
display: block;
position: absolute;
left: 0;
top: 0;
width: 100%;
min-height: 5rem;
max-height: 7rem;
margin: 0;
opacity: 0;
z-index: 2;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.filepond--image-preview-overlay svg {
width: 100%;
height: auto;
color: inherit;
max-height: inherit;
}
.filepond--image-preview-overlay-idle {
mix-blend-mode: multiply;
color: rgba(40, 40, 40, 0.85);
}
.filepond--image-preview-overlay-success {
mix-blend-mode: normal;
color: rgba(54, 151, 99, 1);
}
.filepond--image-preview-overlay-failure {
mix-blend-mode: normal;
color: rgba(196, 78, 71, 1);
}
@supports (-webkit-marquee-repetition: infinite) and ((-o-object-fit: fill) or (object-fit: fill)) {
.filepond--image-preview-overlay-idle {
mix-blend-mode: normal;
}
}
.filepond--image-preview-wrapper {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
position: absolute;
left: 0;
top: 0;
right: 0;
height: 100%;
margin: 0;
border-radius: 0.45em;
overflow: hidden;
background: rgba(0, 0, 0, 0.01);
}
.filepond--image-preview {
position: absolute;
left: 0;
top: 0;
z-index: 1;
display: flex;
align-items: center;
height: 100%;
width: 100%;
pointer-events: none;
background: #222;
will-change: transform, opacity;
}
.filepond--image-clip {
position: relative;
overflow: hidden;
margin: 0 auto;
}
.filepond--image-clip[data-transparency-indicator=grid] img,
.filepond--image-clip[data-transparency-indicator=grid] canvas {
background-color: #fff;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg' fill='%23eee'%3E%3Cpath d='M0 0 H50 V50 H0'/%3E%3Cpath d='M50 50 H100 V100 H50'/%3E%3C/svg%3E");
background-size: 1.25em 1.25em;
}
.filepond--image-bitmap,
.filepond--image-vector {
position: absolute;
left: 0;
top: 0;
will-change: transform;
}
.filepond--root[data-style-panel-layout~=integrated] .filepond--image-preview-wrapper {
border-radius: 0;
}
.filepond--root[data-style-panel-layout~=integrated] .filepond--image-preview {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.filepond--root[data-style-panel-layout~=circle] .filepond--image-preview-wrapper {
border-radius: 99999rem;
}
.filepond--root[data-style-panel-layout~=circle] .filepond--image-preview-overlay {
top: auto;
bottom: 0;
-webkit-transform: scaleY(-1);
transform: scaleY(-1);
}
.filepond--root[data-style-panel-layout~=circle] .filepond--file .filepond--file-action-button[data-align*=bottom]:not([data-align*="center"]) {
margin-bottom: 0.325em;
}
.filepond--root[data-style-panel-layout~=circle] .filepond--file [data-align*=left] {
left: calc(50% - 3em);
}
.filepond--root[data-style-panel-layout~=circle] .filepond--file [data-align*=right] {
right: calc(50% - 3em);
}
.filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=left],
.filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=right] {
margin-bottom: calc(0.325em + 0.1875em);
}
.filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=center] {
margin-top: 0;
margin-bottom: 0.1875em;
margin-left: 0.1875em;
}
/* node_modules/filepond/dist/filepond.min.css */
.filepond--assistant {
position: absolute;
@@ -844,143 +976,6 @@
margin-top: 14px;
}
/* node_modules/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css */
.filepond--image-preview-markup {
position: absolute;
left: 0;
top: 0;
}
.filepond--image-preview-wrapper {
z-index: 2;
}
.filepond--image-preview-overlay {
display: block;
position: absolute;
left: 0;
top: 0;
width: 100%;
min-height: 5rem;
max-height: 7rem;
margin: 0;
opacity: 0;
z-index: 2;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.filepond--image-preview-overlay svg {
width: 100%;
height: auto;
color: inherit;
max-height: inherit;
}
.filepond--image-preview-overlay-idle {
mix-blend-mode: multiply;
color: rgba(40, 40, 40, 0.85);
}
.filepond--image-preview-overlay-success {
mix-blend-mode: normal;
color: rgba(54, 151, 99, 1);
}
.filepond--image-preview-overlay-failure {
mix-blend-mode: normal;
color: rgba(196, 78, 71, 1);
}
@supports (-webkit-marquee-repetition: infinite) and ((-o-object-fit: fill) or (object-fit: fill)) {
.filepond--image-preview-overlay-idle {
mix-blend-mode: normal;
}
}
.filepond--image-preview-wrapper {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
position: absolute;
left: 0;
top: 0;
right: 0;
height: 100%;
margin: 0;
border-radius: 0.45em;
overflow: hidden;
background: rgba(0, 0, 0, 0.01);
}
.filepond--image-preview {
position: absolute;
left: 0;
top: 0;
z-index: 1;
display: flex;
align-items: center;
height: 100%;
width: 100%;
pointer-events: none;
background: #222;
will-change: transform, opacity;
}
.filepond--image-clip {
position: relative;
overflow: hidden;
margin: 0 auto;
}
.filepond--image-clip[data-transparency-indicator=grid] img,
.filepond--image-clip[data-transparency-indicator=grid] canvas {
background-color: #fff;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg' fill='%23eee'%3E%3Cpath d='M0 0 H50 V50 H0'/%3E%3Cpath d='M50 50 H100 V100 H50'/%3E%3C/svg%3E");
background-size: 1.25em 1.25em;
}
.filepond--image-bitmap,
.filepond--image-vector {
position: absolute;
left: 0;
top: 0;
will-change: transform;
}
.filepond--root[data-style-panel-layout~=integrated] .filepond--image-preview-wrapper {
border-radius: 0;
}
.filepond--root[data-style-panel-layout~=integrated] .filepond--image-preview {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.filepond--root[data-style-panel-layout~=circle] .filepond--image-preview-wrapper {
border-radius: 99999rem;
}
.filepond--root[data-style-panel-layout~=circle] .filepond--image-preview-overlay {
top: auto;
bottom: 0;
-webkit-transform: scaleY(-1);
transform: scaleY(-1);
}
.filepond--root[data-style-panel-layout~=circle] .filepond--file .filepond--file-action-button[data-align*=bottom]:not([data-align*="center"]) {
margin-bottom: 0.325em;
}
.filepond--root[data-style-panel-layout~=circle] .filepond--file [data-align*=left] {
left: calc(50% - 3em);
}
.filepond--root[data-style-panel-layout~=circle] .filepond--file [data-align*=right] {
right: calc(50% - 3em);
}
.filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=left],
.filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=right] {
margin-bottom: calc(0.325em + 0.1875em);
}
.filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=center] {
margin-top: 0;
margin-bottom: 0.1875em;
margin-left: 0.1875em;
}
/* packages/forms/resources/css/components/file-upload.css */
.filepond--root {
margin-bottom: 0;
}
/* node_modules/trix/dist/trix.css */
trix-editor {
border: 1px solid #bbb;
@@ -1447,3 +1442,8 @@ trix-editor .attachment__metadata .attachment__size {
flex-basis: 50%;
max-width: 50%;
}
/* packages/forms/resources/css/components/file-upload.css */
.filepond--root {
margin-bottom: 0;
}
+5991 -5993
View File
File diff suppressed because it is too large Load Diff
+22
View File
@@ -0,0 +1,22 @@
const mix = require('laravel-mix');
const tailwindcss = require('tailwindcss');
mix.disableSuccessNotifications()
mix.options({
terser: {
extractComments: false,
},
})
mix.setPublicPath('packages/admin/dist')
mix.setResourceRoot('packages/admin/resources')
mix.sourceMaps()
mix.version()
mix.js('packages/admin/resources/js/app.js', 'packages/admin/dist')
mix
.postCss('packages/admin/resources/css/app.css', 'packages/admin/dist', [
tailwindcss('packages/admin/tailwind.config.js'),
]).options({
processCssUrls: false,
})