├── resources
├── dist
│ ├── .gitkeep
│ ├── backend.css
│ └── backend.js
├── views
│ ├── .gitkeep
│ ├── components
│ │ └── blank-field-wrapper.blade.php
│ ├── modals
│ │ └── generate-an-image.blade.php
│ ├── forms
│ │ └── components
│ │ │ └── image-generator.blade.php
│ ├── partials
│ │ └── svg-defs.blade.php
│ └── livewire
│ │ └── generate-form.blade.php
├── js
│ └── index.js
├── css
│ └── index.css
└── lang
│ ├── pl
│ └── backend.php
│ ├── en
│ └── backend.php
│ ├── es
│ └── backend.php
│ └── de
│ └── backend.php
├── src
├── FilamentImageGeneratorField.php
├── Facades
│ └── FilamentImageGenerator.php
├── Services
│ └── DownloadImageFromUrl.php
├── Contracts
│ └── AIImageGenerator.php
├── Generators
│ ├── OpenAIDallE2.php
│ └── OpenAIDallE3.php
├── Forms
│ └── Components
│ │ └── ImageGenerator.php
├── FilamentImageGeneratorFieldServiceProvider.php
└── Components
│ └── GenerateForm.php
├── CHANGELOG.md
├── postcss.config.cjs
├── LICENSE.md
├── config
└── filament-image-generator-field.php
├── bin
└── build.js
├── composer.json
└── README.md
/resources/dist/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/views/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/js/index.js:
--------------------------------------------------------------------------------
1 | import fslightbox from 'fslightbox';
2 |
--------------------------------------------------------------------------------
/resources/views/components/blank-field-wrapper.blade.php:
--------------------------------------------------------------------------------
1 | {{ $slot }}
2 |
--------------------------------------------------------------------------------
/resources/views/modals/generate-an-image.blade.php:
--------------------------------------------------------------------------------
1 | @livewire('filament-image-generator-field::generate-form')
2 |
--------------------------------------------------------------------------------
/src/FilamentImageGeneratorField.php:
--------------------------------------------------------------------------------
1 | toString() . '.' . $extension;
16 |
17 | Storage::disk($disk)->put($filename, $response->body());
18 |
19 | return $filename;
20 | }
21 |
22 | public function saveToDisk(string $responseBody, string $disk, string $extension = 'jpg'): string
23 | {
24 | $filename = Str::uuid()->toString() . '.' . $extension;
25 |
26 | Storage::disk($disk)->put($filename, $responseBody);
27 |
28 | return $filename;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Contracts/AIImageGenerator.php:
--------------------------------------------------------------------------------
1 |
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/config/filament-image-generator-field.php:
--------------------------------------------------------------------------------
1 | [
7 | 'openai-dall-e-2' => \NaturalGroove\Filament\ImageGeneratorField\Generators\OpenAIDallE2::class,
8 | 'openai-dall-e-3' => \NaturalGroove\Filament\ImageGeneratorField\Generators\OpenAIDallE3::class,
9 | ],
10 |
11 | // Define the default generator to use
12 | 'default-generator' => 'openai-dall-e-3',
13 |
14 | // Define the configuration for each generator
15 | // The key is the name of the generator and the value is an array of configuration options
16 | // The configuration options are specific to each generator
17 | // For example, the OpenAI DALL-E generator requires an API key
18 | 'openai-dall-e' => [
19 | 'api_key' => env('OPEN_AI_DALL_E_API_KEY', null),
20 | 'output-format' => 'png',
21 | ],
22 |
23 | // Define modals configuration options
24 | 'modal' => [
25 | 'generate-form' => [
26 | 'width' => '6xl',
27 | ]
28 | ]
29 | ];
30 |
--------------------------------------------------------------------------------
/bin/build.js:
--------------------------------------------------------------------------------
1 | import esbuild from 'esbuild'
2 |
3 | const isDev = process.argv.includes('--dev')
4 |
5 | async function compile(options) {
6 | const context = await esbuild.context(options)
7 |
8 | if (isDev) {
9 | await context.watch()
10 | } else {
11 | await context.rebuild()
12 | await context.dispose()
13 | }
14 | }
15 |
16 | const defaultOptions = {
17 | define: {
18 | 'process.env.NODE_ENV': isDev ? `'development'` : `'production'`,
19 | },
20 | bundle: true,
21 | mainFields: ['module', 'main'],
22 | platform: 'neutral',
23 | sourcemap: isDev ? 'inline' : false,
24 | sourcesContent: isDev,
25 | treeShaking: true,
26 | target: ['es2020'],
27 | minify: !isDev,
28 | plugins: [{
29 | name: 'watchPlugin',
30 | setup: function (build) {
31 | build.onStart(() => {
32 | console.log(`Build started at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`)
33 | })
34 |
35 | build.onEnd((result) => {
36 | if (result.errors.length > 0) {
37 | console.log(`Build failed at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`, result.errors)
38 | } else {
39 | console.log(`Build finished at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`)
40 | }
41 | })
42 | }
43 | }],
44 | }
45 |
46 | compile({
47 | ...defaultOptions,
48 | entryPoints: ['./resources/js/index.js'],
49 | outfile: './resources/dist/backend.js',
50 | })
51 |
--------------------------------------------------------------------------------
/resources/lang/pl/backend.php:
--------------------------------------------------------------------------------
1 | [
7 | 'generate-using-ai' => 'Generuj za pomocą AI',
8 | 'edit-using-ai' => 'Edytuj za pomocą AI',
9 | ],
10 |
11 | 'form' => [
12 | 'fields' => [
13 | 'showOptions' => 'Pokaż opcje',
14 | 'options' => 'Opcje',
15 |
16 | 'prompt' => 'Polecenie',
17 | 'prompt-placeholder' => 'na przykład: `Kot siedzący na kanapie`. Staraj się być jak najbardziej opisowy.',
18 |
19 | 'n' => 'Liczba generowanych obrazów',
20 | 'aspect_ratio' => 'Proporcje',
21 | 'size' => 'Rozmiar',
22 | 'size-hint' => 'Wybierz rozmiar obrazu.',
23 | 'style' => 'Styl',
24 | 'style-hint' => 'Wybierz styl obrazu.',
25 | 'quality' => 'Jakość',
26 | 'quality-hint' => 'Wybierz jakość obrazu.',
27 | ],
28 |
29 | 'errors' => [
30 | 'no-images-generated' => 'Nie wygenerowano żadnych obrazów. Spróbuj ponownie z innym poleceniem.',
31 | ]
32 | ],
33 |
34 | 'modals' => [
35 | 'generate-an-image' => [
36 | 'title' => 'Generuj obraz',
37 | 'description' => 'Opisz obraz, który chcesz wygenerować.
Poczekaj kilka sekund, aż obraz będzie gotowy.',
38 | 'generate' => 'Generuj',
39 | 'generating' => 'Generowanie...',
40 | 'add-generated' => 'Dodaj wygenerowany obraz',
41 | 'cancel' => 'Anuluj',
42 | 'select' => 'Wybierz',
43 | 'uploading' => 'Przesyłanie...',
44 |
45 | 'configuration-error' => 'Konfiguracja dla wybranego generatora jest nieprawidłowa. Sprawdź konfigurację, taką jak klucz API, i spróbuj ponownie. ',
46 | ]
47 | ]
48 | ];
49 |
--------------------------------------------------------------------------------
/resources/lang/en/backend.php:
--------------------------------------------------------------------------------
1 | [
7 | 'generate-using-ai' => 'Generate using AI',
8 | 'edit-using-ai' => 'Edit using AI',
9 | ],
10 |
11 | 'form' => [
12 | 'fields' => [
13 | 'showOptions' => 'Show Options',
14 | 'options' => 'Options',
15 |
16 | 'prompt' => 'Prompt',
17 | 'prompt-placeholder' => 'for example: `A cat sitting on a couch`. Try to be as descriptive as possible.',
18 |
19 | 'n' => 'Number of Images to Generate',
20 | 'aspect_ratio' => 'Aspect ratio',
21 | 'size' => 'Size',
22 | 'size-hint' => 'Select the size of the image.',
23 | 'style' => 'Style',
24 | 'style-hint' => 'Select the style of the image.',
25 | 'quality' => 'Quality',
26 | 'quality-hint' => 'Select the quality of the image.',
27 | ],
28 |
29 | 'errors' => [
30 | 'no-images-generated' => 'No images were generated. Please try again with a different prompt.',
31 | ]
32 | ],
33 |
34 | 'modals' => [
35 | 'generate-an-image' => [
36 | 'title' => 'Generate an Image',
37 | 'description' => 'Describe the image You want to generate.
Wait for a few seconds until your image is ready.',
38 | 'generate' => 'Generate',
39 | 'generating' => 'Generating...',
40 | 'add-generated' => 'Add generated image',
41 | 'cancel' => 'Cancel',
42 | 'select' => 'Select',
43 | 'uploading' => 'Uploading...',
44 |
45 | 'configuration-error' => 'Configuration for the selected generator is invalid. Please check the configuration like API key and try again. ',
46 | ]
47 | ]
48 | ];
49 |
--------------------------------------------------------------------------------
/resources/lang/es/backend.php:
--------------------------------------------------------------------------------
1 | [
7 | 'generate-using-ai' => 'Generar usando IA',
8 | 'edit-using-ai' => 'Editar usando IA',
9 | ],
10 |
11 | 'form' => [
12 | 'fields' => [
13 | 'showOptions' => 'Mostrar opciones',
14 | 'options' => 'Opciones',
15 |
16 | 'prompt' => 'Indicación',
17 | 'prompt-placeholder' => 'por ejemplo: `Un gato sentado en un sofá`. Intenta ser lo más descriptivo posible.',
18 |
19 | 'n' => 'Número de imágenes a generar',
20 | 'aspect_ratio' => 'Relación de aspecto',
21 | 'size' => 'Tamaño',
22 | 'size-hint' => 'Selecciona el tamaño de la imagen.',
23 | 'style' => 'Estilo',
24 | 'style-hint' => 'Selecciona el estilo de la imagen.',
25 | 'quality' => 'Calidad',
26 | 'quality-hint' => 'Selecciona la calidad de la imagen.',
27 | ],
28 |
29 | 'errors' => [
30 | 'no-images-generated' => 'No se generaron imágenes. Por favor, intenta de nuevo con una indicación diferente.',
31 | ]
32 | ],
33 |
34 | 'modals' => [
35 | 'generate-an-image' => [
36 | 'title' => 'Generar una imagen',
37 | 'description' => 'Describe la imagen que quieres generar.
Espera unos segundos hasta que tu imagen esté lista.',
38 | 'generate' => 'Generar',
39 | 'generating' => 'Generando...',
40 | 'add-generated' => 'Agregar imagen generada',
41 | 'cancel' => 'Cancelar',
42 | 'select' => 'Seleccionar',
43 | 'uploading' => 'Subiendo...',
44 |
45 | 'configuration-error' => 'La configuración para el generador seleccionado no es válida. Por favor, verifica la configuración como la clave de API e intenta nuevamente. ',
46 | ]
47 | ]
48 | ];
49 |
--------------------------------------------------------------------------------
/resources/lang/de/backend.php:
--------------------------------------------------------------------------------
1 | [
7 | 'generate-using-ai' => 'Mit AI generieren',
8 | 'edit-using-ai' => 'Mit AI bearbeiten',
9 | ],
10 |
11 | 'form' => [
12 | 'fields' => [
13 | 'showOptions' => 'Optionen anzeigen',
14 | 'options' => 'Optionen',
15 |
16 | 'prompt' => 'Aufforderung',
17 | 'prompt-placeholder' => 'zum Beispiel: `Eine Katze sitzt auf einem Sofa`. Versuche so genau wie möglich zu sein.',
18 |
19 | 'n' => 'Anzahl der zu generierenden Bilder',
20 | 'aspect_ratio' => 'Seitenverhältnis',
21 | 'size' => 'Größe',
22 | 'size-hint' => 'Wähle die Größe des Bildes aus.',
23 | 'style' => 'Stil',
24 | 'style-hint' => 'Wähle den Stil des Bildes aus.',
25 | 'quality' => 'Qualität',
26 | 'quality-hint' => 'Wähle die Qualität des Bildes aus.',
27 | ],
28 |
29 | 'errors' => [
30 | 'no-images-generated' => 'Es wurden keine Bilder generiert. Bitte versuche es erneut mit einer anderen Aufforderung.',
31 | ]
32 | ],
33 |
34 | 'modals' => [
35 | 'generate-an-image' => [
36 | 'title' => 'Bild generieren',
37 | 'description' => 'Beschreibe das Bild, das du generieren möchtest.
Warte einige Sekunden, bis dein Bild fertig ist.',
38 | 'generate' => 'Generieren',
39 | 'generating' => 'Generiere...',
40 | 'add-generated' => 'Generiertes Bild hinzufügen',
41 | 'cancel' => 'Abbrechen',
42 | 'select' => 'Auswählen',
43 | 'uploading' => 'Hochladen...',
44 |
45 | 'configuration-error' => 'Die Konfiguration für den ausgewählten Generator ist ungültig. Bitte überprüfen Sie die Konfiguration, wie z.B. den API-Schlüssel, und versuchen Sie es erneut.'
46 | ]
47 | ]
48 | ];
49 |
--------------------------------------------------------------------------------
/resources/views/forms/components/image-generator.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 | @include('filament-forms::components.file-upload')
16 |
17 |
18 |
19 |
23 |
24 |
27 |
28 | {{ Str::ucfirst(__('filament-image-generator-field::backend.labels.generate-using-ai')) }}
29 |
30 |
31 |
32 |
39 |
40 |
43 |
44 | {{ Str::ucfirst(__('filament-image-generator-field::backend.labels.edit-using-ai')) }}
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/Generators/OpenAIDallE2.php:
--------------------------------------------------------------------------------
1 | getOpenAIClient();
17 |
18 | $response = $client->images()->create([
19 | 'model' => 'dall-e-2',
20 | 'prompt' => $prompt,
21 | 'n' => $n,
22 | 'size' => '1024x1024', // '1024x1024' is the only supported aspect ratio for DALL-E 2
23 | 'response_format' => 'url',
24 | ]);
25 |
26 | //Log::info('OpenAI Dall-E response', $response->toArray());
27 |
28 | return $response->toArray()['data'];
29 | }
30 |
31 | public function fetch(string $url): string
32 | {
33 | $response = Http::get($url);
34 |
35 | return $response->body();
36 | }
37 | public function getName(): string
38 | {
39 | return static::$name;
40 | }
41 |
42 | public function getFileExtension(): string
43 | {
44 | return config('filament-image-generator-field.openai-dall-e.file_extension') ?? 'png';
45 | }
46 |
47 | public function getSupportedOptions(): array
48 | {
49 | return [
50 | 'n' => [
51 | '1' => '1',
52 | '2' => '2',
53 | '4' => '4',
54 | ],
55 |
56 | 'aspect_ratio' => [
57 | '1:1' => '1:1',
58 | ]
59 | ];
60 | }
61 |
62 | public function validateConfiguration(): bool
63 | {
64 | return config('filament-image-generator-field.openai-dall-e.api_key') !== null;
65 | }
66 |
67 | // ************************************************************
68 |
69 | protected function getOpenAIClient(): OpenAI\Client
70 | {
71 | return OpenAI::client(config('filament-image-generator-field.openai-dall-e.api_key'));
72 | }
73 |
74 | protected function matchAspectRatio(string $aspectRatio): string
75 | {
76 | $aspectRatios = [
77 | '1:1' => '1024x1024',
78 | ];
79 |
80 | return $aspectRatios[$aspectRatio] ?? '1024x1024';
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Forms/Components/ImageGenerator.php:
--------------------------------------------------------------------------------
1 | image();
24 | $this->columnSpanFull();
25 |
26 | // register the modal view with component - prevent multiple registration
27 | if (!static::$isComponentRegistered) {
28 | FilamentView::registerRenderHook(
29 | PanelsRenderHook::CONTENT_END,
30 | fn (): View => view('filament-image-generator-field::modals.generate-an-image'),
31 | );
32 |
33 | static::$isComponentRegistered = true;
34 | }
35 |
36 | $this->imageGenerator(config('filament-image-generator-field.default-generator'));
37 | }
38 |
39 | public function getFieldWrapperView(?string $scope = null): string
40 | {
41 | if ($scope === 'generator') {
42 | return $this->getCustomFieldWrapperView() ??
43 | $this->getContainer()->getCustomFieldWrapperView() ??
44 | 'filament-forms::field-wrapper';
45 | }
46 |
47 | return 'filament-image-generator-field::blank-field-wrapper';
48 | }
49 |
50 | public function imageGenerator(string | Closure | null $imageGenerator): static
51 | {
52 | if (is_string($imageGenerator) && !config("filament-image-generator-field.generators.{$imageGenerator}")) {
53 | throw new \InvalidArgumentException(__('Image generator `{$imageGenerator}` is not defined in the configuration..'));
54 | }
55 |
56 | $this->imageGenerator = $imageGenerator;
57 |
58 | return $this;
59 | }
60 |
61 | public function getImageGenerator(): ?string
62 | {
63 | return $this->evaluate($this->imageGenerator);
64 | }
65 |
66 | public function useDallE2(): static
67 | {
68 | return $this->imageGenerator('openai-dall-e-2');
69 | }
70 |
71 | public function useDallE3(): static
72 | {
73 | return $this->imageGenerator('openai-dall-e-3');
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/resources/views/partials/svg-defs.blade.php:
--------------------------------------------------------------------------------
1 | {{-- viewBox="0 0 380 379" --}}
2 |
28 |
--------------------------------------------------------------------------------
/src/Generators/OpenAIDallE3.php:
--------------------------------------------------------------------------------
1 | getOpenAIClient();
17 |
18 | $response = $client->images()->create([
19 | 'model' => 'dall-e-3',
20 | 'prompt' => $prompt,
21 | 'n' => 1,
22 | 'size' => $this->matchAspectRatio($params['aspect_ratio']),
23 | 'response_format' => 'url',
24 | 'quality' => $params['quality'] ?? 'standard',
25 | 'style' => $params['style'] ?? 'natural',
26 | ]);
27 |
28 | //Log::info('OpenAI Dall-E response', $response->toArray());
29 |
30 | return $response->toArray()['data'];
31 | }
32 |
33 | public function fetch(string $url): string
34 | {
35 | $response = Http::get($url);
36 |
37 | return $response->body();
38 | }
39 |
40 | public function getName(): string
41 | {
42 | return static::$name;
43 | }
44 |
45 | public function getFileExtension(): string
46 | {
47 | return config('filament-image-generator-field.openai-dall-e.file_extension') ?? 'png';
48 | }
49 |
50 | public function getSupportedOptions(): array
51 | {
52 | return [
53 | 'aspect_ratio' => [
54 | '1:1' => '1:1',
55 | '16:9' => '16:9',
56 | '9:16' => '9:16',
57 | ],
58 |
59 | 'quality' => [
60 | 'standard' => 'Standard',
61 | 'hd' => 'High definition',
62 | ],
63 |
64 | 'style' => [
65 | 'natural' => 'Natural',
66 | 'vivid' => 'Vivid',
67 | ],
68 | ];
69 | }
70 |
71 | public function validateConfiguration(): bool
72 | {
73 | return config('filament-image-generator-field.openai-dall-e.api_key') !== null;
74 | }
75 |
76 | // ************************************************************
77 |
78 | protected function getOpenAIClient(): OpenAI\Client
79 | {
80 | return OpenAI::client(config('filament-image-generator-field.openai-dall-e.api_key'));
81 | }
82 |
83 | protected function matchAspectRatio(string $aspectRatio): string
84 | {
85 | $aspectRatios = [
86 | '1:1' => '1024x1024',
87 | '16:9' => '1792x1024',
88 | '9:16' => '1024x1792',
89 | ];
90 |
91 | return $aspectRatios[$aspectRatio] ?? '1024x1024';
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/FilamentImageGeneratorFieldServiceProvider.php:
--------------------------------------------------------------------------------
1 | name(static::$name)
28 | ->hasConfigFile()
29 | ->hasTranslations()
30 | ->hasViews();
31 |
32 | $package->hasInstallCommand(function (InstallCommand $command) {
33 | $command
34 | ->startWith(function (InstallCommand $command) {
35 | $command->info('Hello, and welcome to Filament ImageGeneratorField installation!');
36 |
37 | if ($command->confirm('Would you like to publish the config file?', false)) {
38 | $command->callSilent('vendor:publish', ['--tag' => 'filament-image-generator-field', '--force' => true]);
39 | }
40 |
41 | if ($command->confirm('Would you like to publish the translations?', false)) {
42 | $command->callSilent('vendor:publish', ['--tag' => 'filament-image-generator-field', '--force' => true]);
43 | }
44 | })
45 | ->publishAssets()
46 | ->endWith(function (InstallCommand $command) {
47 | $command->info('Have a great day!');
48 | });
49 | });
50 |
51 | Livewire::component('filament-image-generator-field::generate-form', GenerateForm::class);
52 |
53 | // Asset Registration
54 | FilamentAsset::register(
55 | $this->getAssets(),
56 | $this->getAssetPackageName()
57 | );
58 | }
59 |
60 | /**
61 | * @return array<\Filament\Support\Assets\Asset>>
62 | */
63 | protected function getAssets(): array
64 | {
65 | return [
66 | Css::make('backend', __DIR__ . '/../resources/dist/backend.css')->loadedOnRequest(),
67 | Js::make('backend', __DIR__ . '/../resources/dist/backend.js')->loadedOnRequest()
68 |
69 | ];
70 | }
71 |
72 | protected function getAssetPackageName(): string
73 | {
74 | return 'naturalgroove/filament-image-generator-field';
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "naturalgroove/laravel-filament-image-generator-field",
3 | "description": "A Laravel Filament plugin that generates images using AI directly in Admin Panel",
4 | "keywords": [
5 | "naturalGroove",
6 | "laravel",
7 | "laravel-filament-cms",
8 | "laravel-filament",
9 | "laravel-filament-plugin",
10 | "laravel-filament-image-generator-field",
11 | "ai-image-generator"
12 | ],
13 | "homepage": "https://github.com/naturalgroove/laravel-filament-image-generator-field",
14 | "support": {
15 | "issues": "https://github.com/naturalgroove/laravel-filament-image-generator-field/issues",
16 | "source": "https://github.com/naturalgroove/laravel-filament-image-generator-field"
17 | },
18 | "license": "MIT",
19 | "authors": [
20 | {
21 | "name": "Grzegorz Adamczyk",
22 | "email": "naturalgroove@gmail.com",
23 | "role": "Developer"
24 | }
25 | ],
26 | "require": {
27 | "php": "^8.1",
28 | "filament/forms": "^3.0",
29 | "guzzlehttp/guzzle": "^7.8",
30 | "openai-php/client": "^0.8.5|^0.9.0|^0.10.0",
31 | "spatie/laravel-package-tools": "^1.15.0",
32 | "symfony/http-client": "^6.4|^7.1.4"
33 | },
34 | "require-dev": {
35 | "laravel/pint": "^1.0",
36 | "nunomaduro/collision": "^7.9",
37 | "larastan/larastan": "^2.0.1",
38 | "orchestra/testbench": "^8.0",
39 | "pestphp/pest": "^2.1",
40 | "pestphp/pest-plugin-arch": "^2.0",
41 | "pestphp/pest-plugin-laravel": "^2.0",
42 | "phpstan/extension-installer": "^1.1",
43 | "phpstan/phpstan-deprecation-rules": "^1.0",
44 | "phpstan/phpstan-phpunit": "^1.0"
45 | },
46 | "autoload": {
47 | "psr-4": {
48 | "NaturalGroove\\Filament\\ImageGeneratorField\\": "src/"
49 | }
50 | },
51 | "autoload-dev": {
52 | "psr-4": {
53 | "NaturalGroove\\Filament\\ImageGeneratorField\\Tests\\": "tests/"
54 | }
55 | },
56 | "scripts": {
57 | "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi",
58 | "analyse": "vendor/bin/phpstan analyse",
59 | "test": "vendor/bin/pest",
60 | "test-coverage": "vendor/bin/pest --coverage",
61 | "format": "vendor/bin/pint"
62 | },
63 | "config": {
64 | "sort-packages": true,
65 | "allow-plugins": {
66 | "pestphp/pest-plugin": true,
67 | "phpstan/extension-installer": true,
68 | "php-http/discovery": true
69 | }
70 | },
71 | "extra": {
72 | "laravel": {
73 | "providers": [
74 | "NaturalGroove\\Filament\\ImageGeneratorField\\FilamentImageGeneratorFieldServiceProvider"
75 | ],
76 | "aliases": {
77 | "FilamentImageGenerator": "NaturalGroove\\Filament\\ImageGeneratorField\\Facades\\ImageGeneratorField"
78 | }
79 | }
80 | },
81 | "minimum-stability": "dev",
82 | "prefer-stable": true
83 | }
84 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Image Generator Form Field for Laravel Filament
2 |
3 | [](https://packagist.org/packages/naturalGroove/laravel-filament-image-generator-field)
4 | [](https://github.com/naturalGroove/laravel-filament-image-generator-field/actions?query=workflow%3Arun-tests+branch%3Amain)
5 | [](https://github.com/naturalGroove/laravel-filament-image-generator-field/actions?query=workflow%3A"Fix+PHP+code+styling"+branch%3Amain)
6 | [](https://packagist.org/packages/naturalGroove/laravel-filament-image-generator-field)
7 |
8 | This custom field allows you to generate images with different sizes and formats using AI models like OpenAI DALL-E.
9 | It extends the FileUpload field and adds a button to open the image generator modal where you can set the sizes and formats of the generated images.
10 |
11 |
12 |
13 | # Installation
14 |
15 | Before you begin, you must have the Laravel Filament package installed and configured. If you haven't done this yet, you can find the installation instructions [here](https://filamentadmin.com/docs/installation).
16 |
17 | ## Prerequisites
18 |
19 | Default Image Generator is set to OpenAI DALL-E (version 3). You should have an API key to use it. You can get it [here](https://platform.openai.com/).
20 | After You get the API key, you should set it in your .env file:
21 |
22 | ```bash
23 | OPEN_AI_DALL_E_API_KEY=your-api-key
24 | ```
25 |
26 | ## Install the package via composer
27 |
28 | Run the following command in your terminal to install the package:
29 |
30 | ```bash
31 | composer require naturalGroove/laravel-filament-image-generator-field
32 | ```
33 |
34 | You can publish the config file with:
35 |
36 | ```bash
37 | php artisan vendor:publish --tag="filament-image-generator-field-config"
38 | ```
39 |
40 | Configuration file lets you set the default image generator and the available image generators for the field.
41 |
42 | Optionally, you can publish the views to customize the field:
43 |
44 | ```bash
45 | php artisan vendor:publish --tag="filament-image-generator-field-views"
46 | ```
47 |
48 | ## Usage
49 |
50 | 
51 |
52 | Just add new Field or replace your FileUpload field with ImageGenerator field in your form schema definition:
53 |
54 | ```php
55 | use \NaturalGroove\Filament\ImageGeneratorField\Forms\Components\ImageGenerator;
56 |
57 | [...]
58 | public static function form(Form $form): Form
59 | {
60 | return $form
61 | ->schema([
62 | ImageGenerator::make('photo'),
63 | ]);
64 | ```
65 |
66 | If You are replacing the FileUpload field:
67 |
68 | ```diff
69 | use \NaturalGroove\Filament\ImageGeneratorField\Forms\Components\ImageGenerator;
70 |
71 | [...]
72 | - FileUpload::make('photo'),
73 | + ImageGenerator::make('photo'),
74 | ```
75 |
76 | You could use all the same options as FileUpload field, for example:
77 |
78 | ```php
79 | use \NaturalGroove\Filament\ImageGeneratorField\Forms\Components\ImageGenerator;
80 |
81 | ImageGenerator::make('photo')
82 | ->imageEditor()
83 | ->disk('private'),
84 | ```
85 |
86 | This plugin comes with a default image generator set to OpenAI DALL-E.
87 | You can select which version of the model you want to use when defining the field:
88 |
89 | ```php
90 | ImageGenerator::make('photo'
91 | ->imageGenerator('openai-dall-e-3'),
92 | ```
93 |
94 | There are predefined shortcuts for the image generators:
95 |
96 | ```php
97 | ImageGenerator::make('photo')
98 | ->openaiDallE2(); // equivalent to ->imageGenerator('openai-dall-e-2')
99 |
100 | ImageGenerator::make('photo')
101 | ->openaiDallE3(); // equivalent to ->imageGenerator('openai-dall-e-3')
102 | ```
103 |
104 | Depending on the image generator you choose, there are different options you can set. For example Dall-E 2 allows to set the number of images generated.
105 |
106 | After you add the field to your form, you should see a button next to the file input. When you click the button, the image generator modal will open.
107 |
108 | 
109 |
110 | ## Adding custom image generators
111 |
112 | You can add custom image generators by impolemeting the `NaturalGroove\Filament\ImageGeneratorField\Contracts\AIImageGenerator` interface.
113 | Your class should have the all the methods from the interface and should be registered in the config file.
114 | Format of returned array should be the same as in the example below:
115 |
116 | ```php
117 | use NaturalGroove\Filament\ImageGeneratorField\Contracts\AIImageGenerator;
118 |
119 | class MyCustomImageGenerator implements AIImageGenerator
120 | {
121 | public function generate(string $prompt, int $n = 1, array $params = []): array
122 | {
123 | // your implementation
124 |
125 | return [
126 | [
127 | 'url' => 'https://example.com/image.jpg'
128 | ]
129 | [...]
130 | ];
131 | }
132 |
133 | }
134 | ```
135 |
136 | 
137 |
138 | ## Upcoming features
139 |
140 | - [ ] Add more image generators
141 | - [ ] Add functionality to edit your uploaded image with AI models (img2img)
142 | - [ ] Add more options to the field
143 |
144 | ## Changelog
145 |
146 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
147 |
148 | ## Contributing
149 |
150 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.
151 |
152 | ## Security Vulnerabilities
153 |
154 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities.
155 |
156 | ## Credits
157 |
158 | - [Grzegorz Adamczyk](https://github.com/naturalGroove)
159 | - [All Contributors](../../contributors)
160 |
161 | ## License
162 |
163 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
164 |
--------------------------------------------------------------------------------
/resources/views/livewire/generate-form.blade.php:
--------------------------------------------------------------------------------
1 |
48 |
55 |
56 | {{ __('filament-image-generator-field::backend.modals.generate-an-image.title') }}
57 | ({{ $generatorName }})
58 |
59 |
60 |
61 | @if ($isConfigurationOk)
62 |
63 |
64 |
65 |
66 |
69 |
70 |
71 |
74 | @if (count($generatedImages) === 1)
75 |
81 |
82 |
83 | @elseif (count($generatedImages) > 1)
84 | @foreach ($generatedImages as $key => $generatedImage)
85 |
86 |
87 |
88 |
89 |
90 |
91 | @lang('filament-image-generator-field::backend.modals.generate-an-image.select')
92 |
93 |
94 | @endforeach
95 | @else
96 | @endif
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | {!! __('filament-image-generator-field::backend.modals.generate-an-image.description') !!}
106 |
107 |
108 |
109 |
110 | {{ $this->promptForm }}
111 |
112 |
113 |
114 |
115 |
116 |
117 |
120 |
121 | {{ Str::ucfirst(__('filament-image-generator-field::backend.modals.generate-an-image.generate')) }}
122 | {{ Str::ucfirst(__('filament-image-generator-field::backend.modals.generate-an-image.generating')) }}
123 |
124 |
125 |
126 |
127 |
128 | @else
129 |
130 |
131 |
132 | {!! __('filament-image-generator-field::backend.modals.generate-an-image.configuration-error') !!}
133 |
134 |
135 |
136 | @endif
137 |
138 |
139 |
140 |
141 |
142 |
143 | {{ Str::ucfirst(__('filament-image-generator-field::backend.modals.generate-an-image.cancel')) }}
144 |
145 |
146 |
152 |
153 |
154 |
155 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 | @include('filament-image-generator-field::partials.svg-defs')
167 |
168 |
180 |
181 |
--------------------------------------------------------------------------------
/src/Components/GenerateForm.php:
--------------------------------------------------------------------------------
1 | isConfigurationOk = $this->getGeneratorObject(config('filament-image-generator-field.default-generator'))->validateConfiguration();
45 |
46 | $this->updateImageGenerator(config('filament-image-generator-field.default-generator'));
47 | }
48 |
49 | public function render(): View
50 | {
51 | return view('filament-image-generator-field::livewire.generate-form');
52 | }
53 |
54 | protected function getForms(): array
55 | {
56 | return [
57 | 'promptForm' => $this->makeForm()
58 | ->columns(2)
59 | ->schema([
60 | Textarea::make('prompt')
61 | ->translateLabel()
62 | ->label('filament-image-generator-field::backend.form.fields.prompt')
63 | ->columnSpan(2)
64 | ->placeholder(__('filament-image-generator-field::backend.form.fields.prompt-placeholder'))
65 | ->rows(5)
66 | ->rules(['string', 'min:10'])
67 | ->default($this->prompt)
68 | ->required(),
69 | Checkbox::make('showOptions')
70 | ->translateLabel()
71 | ->label('filament-image-generator-field::backend.form.fields.showOptions')
72 | ->default(false)
73 | ->live(),
74 | Fieldset::make(__('filament-image-generator-field::backend.form.fields.options'))
75 | ->visible(fn (Get $get): bool => $get('showOptions'))
76 | ->schema([
77 | Select::make('n')
78 | ->translateLabel()
79 | ->label('filament-image-generator-field::backend.form.fields.n')
80 | ->options(fn () => $this->getGeneratorObject($this->generatorName)->getSupportedOptions()['n'] ?? [1 => 1])
81 | ->visible(fn (): bool => isset($this->getGeneratorObject($this->generatorName)->getSupportedOptions()['n']))
82 | ->rules(['integer'])
83 | ->default(1),
84 | Select::make('aspect_ratio')
85 | ->translateLabel()
86 | ->label('filament-image-generator-field::backend.form.fields.aspect_ratio')
87 | ->options(fn () => $this->getGeneratorObject($this->generatorName)->getSupportedOptions()['aspect_ratio'] ?? [])
88 | ->visible(fn (): bool => isset($this->getGeneratorObject($this->generatorName)->getSupportedOptions()['aspect_ratio']))
89 | ->rules(['string'])
90 | ->required(),
91 | Select::make('size')
92 | ->translateLabel()
93 | ->label('filament-image-generator-field::backend.form.fields.size')
94 | ->hint(__('filament-image-generator-field::backend.form.fields.size-hint'))
95 | ->options(fn () => $this->getGeneratorObject($this->generatorName)->getSupportedOptions()['size'] ?? [])
96 | ->visible(fn (): bool => isset($this->getGeneratorObject($this->generatorName)->getSupportedOptions()['size']))
97 | ->rules(['string'])
98 | ->required(),
99 | Select::make('style')
100 | ->translateLabel()
101 | ->label('filament-image-generator-field::backend.form.fields.style')
102 | ->hint(__('filament-image-generator-field::backend.form.fields.style-hint'))
103 | ->options(fn () => $this->getGeneratorObject($this->generatorName)->getSupportedOptions()['style'] ?? [])
104 | ->visible(fn (): bool => isset($this->getGeneratorObject($this->generatorName)->getSupportedOptions()['style']))
105 | ->rules(['string'])
106 | ->required(),
107 | Select::make('quality')
108 | ->translateLabel()
109 | ->label('filament-image-generator-field::backend.form.fields.quality')
110 | ->hint(__('filament-image-generator-field::backend.form.fields.quality-hint'))
111 | ->options(fn () => $this->getGeneratorObject($this->generatorName)->getSupportedOptions()['quality'] ?? [])
112 | ->visible(fn (): bool => isset($this->getGeneratorObject($this->generatorName)->getSupportedOptions()['quality']))
113 | ->rules(['string'])
114 | ->required(),
115 | ])
116 | ->columns(1)
117 | ])
118 | ];
119 | }
120 |
121 | public function generateImage(string $generator): void
122 | {
123 | // empty the generated images array
124 | $this->generatedImages = [];
125 | $this->url = null;
126 |
127 | $this->validate();
128 |
129 | $this->verifyGenerator($generator);
130 |
131 | try {
132 | $this->generator = $this->getGeneratorObject($generator);
133 |
134 | $this->generatedImages = $this->generator->generate($this->prompt ?? '', $this->n, [
135 | 'aspect_ratio' => $this->aspect_ratio ?? null,
136 | 'size' => $this->size ?? null,
137 | 'quality' => $this->quality ?? null,
138 | 'style' => $this->style ?? null,
139 | ]);
140 |
141 | $this->processGeneratedImages();
142 | } catch (\Exception $e) {
143 | $this->addError('prompt', $e->getMessage());
144 | }
145 | }
146 |
147 | public function selectImage(int $index): void
148 | {
149 | $this->url = $this->generatedImages[$index]['url'];
150 | }
151 |
152 | #[On('update-image-generator')]
153 | public function updateImageGenerator(string $generator): void
154 | {
155 | if ($this->generatorName !== $generator) {
156 | $this->verifyGenerator($generator);
157 |
158 | $this->generatorName = $generator;
159 | $this->generator = $this->getGeneratorObject($this->generatorName);
160 |
161 | // check if the configuration for the generator is ok
162 | $this->isConfigurationOk = $this->getGeneratorObject(config('filament-image-generator-field.default-generator'))->validateConfiguration();
163 |
164 | // set the default values for the form fields
165 | $defaultFields = [];
166 |
167 | // set the default values for the form fields
168 | foreach ($this->generator->getSupportedOptions() as $key => $options) {
169 | $this->{$key} = array_key_first($options);
170 | $defaultFields[$key] = $this->{$key};
171 | }
172 |
173 | $this->showOptions = false;
174 |
175 | $this->getForm('promptForm')?->fill(
176 | array_merge(
177 | $defaultFields,
178 | [
179 | 'n' => $this->n
180 | ]
181 | )
182 | );
183 | }
184 | }
185 |
186 | #[On('add-selected')]
187 | public function addSelected(array $image, string $statePath, string $disk, string $generator): void
188 | {
189 | $this->generator = $this->getGeneratorObject($generator);
190 |
191 | $localFileName = (new DownloadImageFromUrl())->saveToDisk($this->generator->fetch($image['url']), $disk, $this->generator->getFileExtension());
192 |
193 | $this->dispatch('generated-image-uploaded', uuid: Str::uuid()->toString(), localFileName: $localFileName, statePath: $statePath);
194 | }
195 |
196 | protected function verifyGenerator(string $generator): void
197 | {
198 | if (config("filament-image-generator-field.generators.{$generator}") === null) {
199 | throw new \InvalidArgumentException("Image generator `{$generator}` is not defined in the configuration.");
200 | }
201 | }
202 |
203 | protected function getGeneratorObject(?string $generator): AIImageGenerator
204 | {
205 | if ($this->generator === null) {
206 | // @phpstan-ignore-next-line
207 | $this->generator = new (config("filament-image-generator-field.generators.{$generator}"))();
208 | }
209 |
210 | // @phpstan-ignore-next-line
211 | return $this->generator;
212 | }
213 |
214 | protected function processGeneratedImages(): void
215 | {
216 | if (count($this->generatedImages) === 0) {
217 | $this->addError('prompt', __('filament-image-generator-field::backend.form.errors.no-images-generated'));
218 | }
219 |
220 | if (count($this->generatedImages) === 1) {
221 | $this->url = $this->generatedImages[0]['url'];
222 | }
223 |
224 | if (count($this->generatedImages) > 1) {
225 | // TODO: Implement multiple images - if needed
226 | }
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/resources/dist/backend.css:
--------------------------------------------------------------------------------
1 | /*! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border-width:0;border-style:solid;border-color:rgba(var(--gray-200),1)}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:rgba(var(--gray-400),1)}input::placeholder,textarea::placeholder{opacity:1;color:rgba(var(--gray-400),1)}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],input:where(:not([type])),select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:rgba(var(--gray-500),var(--tw-border-opacity,1));border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow:0 0 #0000}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,input:where(:not([type])):focus,select:focus,textarea:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:rgba(var(--gray-500),var(--tw-text-opacity,1));opacity:1}input::placeholder,textarea::placeholder{color:rgba(var(--gray-500),var(--tw-text-opacity,1));opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='rgba(var(--gray-500), var(--tw-stroke-opacity, 1))' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple],[size]:where(select:not([size="1"])){background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:rgba(var(--gray-500),var(--tw-border-opacity,1));border-width:1px;--tw-shadow:0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:#0000;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E")}@media (forced-colors:active){[type=checkbox]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}@media (forced-colors:active){[type=radio]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=checkbox]:indeterminate,[type=radio]:checked:focus,[type=radio]:checked:hover{border-color:#0000;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");background-size:100% 100%;background-position:50%;background-repeat:no-repeat}@media (forced-colors:active){[type=checkbox]:indeterminate{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{border-color:#0000;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}:root.dark{color-scheme:dark}[data-field-wrapper]{scroll-margin-top:8rem}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.prose :where(.prose>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(.prose>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose-sm :where(.prose-sm>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(.prose-sm>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.1428571em}.prose-sm :where(.prose-sm>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(.prose-sm>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.1428571em}.prose-base :where(.prose-base>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose-base :where(.prose-base>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose-base :where(.prose-base>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose-base :where(.prose-base>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose-lg :where(.prose-lg>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.3333333em}.prose-lg :where(.prose-lg>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.3333333em}.prose-lg :where(.prose-lg>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.3333333em}.prose-lg :where(.prose-lg>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.3333333em}.\!static{position:static!important}.mb-8{margin-bottom:2rem}.mr-2{margin-right:.5rem}.mt-4{margin-top:1rem}.h-48{height:12rem}.animate-\[spin_5s_linear_infinite\]{animation:spin 5s linear infinite}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.place-content-center{place-content:center}.border-4{border-width:4px}.\!border-primary-500{--tw-border-opacity:1!important;border-color:rgba(var(--primary-500),var(--tw-border-opacity))!important}.bg-blue-100{--tw-bg-opacity:1;background-color:rgb(219 234 254/var(--tw-bg-opacity))}.bg-red-400{--tw-bg-opacity:1;background-color:rgb(248 113 113/var(--tw-bg-opacity))}.object-contain{-o-object-fit:contain;object-fit:contain}.text-blue-900{--tw-text-opacity:1;color:rgb(30 58 138/var(--tw-text-opacity))}.text-neutral-100{--tw-text-opacity:1;color:rgb(245 245 245/var(--tw-text-opacity))}.btn-ig-edit,.btn-ig-generate{color:#fff!important}.btn-ig-generate{background-color:#7a4fa2!important}.btn-ig-generate:hover{background-color:#ac7dd2!important}.btn-ig-edit{background-color:#ad2160!important}.btn-ig-edit:hover{background-color:#e05297!important}.focus-visible\:ring-1:focus-visible,.focus-visible\:ring-2:focus-visible{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.disabled\:placeholder\:\[-webkit-text-fill-color\:theme\(colors\.gray\.400\)\]:disabled::-moz-placeholder,.disabled\:placeholder\:\[-webkit-text-fill-color\:theme\(colors\.gray\.400\)\]:disabled::placeholder{-webkit-text-fill-color:rgba(var(--gray-400),1)}.group\/item:focus-visible .group-focus-visible\/item\:underline,.group\/link:focus-visible .group-focus-visible\/link\:underline{text-decoration-line:underline}.dark\:bg-blue-800:is(.dark *){--tw-bg-opacity:1;background-color:rgb(30 64 175/var(--tw-bg-opacity))}.dark\:bg-neutral-800:is(.dark *){--tw-bg-opacity:1;background-color:rgb(38 38 38/var(--tw-bg-opacity))}.dark\:text-blue-100:is(.dark *){--tw-text-opacity:1;color:rgb(219 234 254/var(--tw-text-opacity))}.dark\:disabled\:placeholder\:\[-webkit-text-fill-color\:theme\(colors\.gray\.500\)\]:disabled:is(.dark *)::-moz-placeholder,.dark\:disabled\:placeholder\:\[-webkit-text-fill-color\:theme\(colors\.gray\.500\)\]:disabled:is(.dark *)::placeholder{-webkit-text-fill-color:rgba(var(--gray-500),1)}@media (min-width:1024px){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}}:checked+*>.\[\:checked\+\*\>\&\]\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}input:checked+.\[input\:checked\+\&\]\:bg-custom-600{--tw-bg-opacity:1;background-color:rgba(var(--c-600),var(--tw-bg-opacity))}input:checked+.\[input\:checked\+\&\]\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}input:checked+.\[input\:checked\+\&\]\:ring-0{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}input:checked+.\[input\:checked\+\&\]\:hover\:bg-custom-500:hover,input:checked+.dark\:\[input\:checked\+\&\]\:bg-custom-500:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--c-500),var(--tw-bg-opacity))}input:checked+.dark\:\[input\:checked\+\&\]\:hover\:bg-custom-400:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--c-400),var(--tw-bg-opacity))}input:checked:focus-visible+.\[input\:checked\:focus-visible\+\&\]\:ring-custom-500\/50{--tw-ring-color:rgba(var(--c-500),0.5)}input:checked:focus-visible+.dark\:\[input\:checked\:focus-visible\+\&\]\:ring-custom-400\/50:is(.dark *){--tw-ring-color:rgba(var(--c-400),0.5)}input:focus-visible+.\[input\:focus-visible\+\&\]\:z-10{z-index:10}input:focus-visible+.\[input\:focus-visible\+\&\]\:ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}input:focus-visible+.\[input\:focus-visible\+\&\]\:ring-gray-950\/10{--tw-ring-color:rgba(var(--gray-950),0.1)}input:focus-visible+.dark\:\[input\:focus-visible\+\&\]\:ring-white\/20:is(.dark *){--tw-ring-color:#fff3}
--------------------------------------------------------------------------------
/resources/dist/backend.js:
--------------------------------------------------------------------------------
1 | var Re=Object.create;var re=Object.defineProperty;var De=Object.getOwnPropertyDescriptor;var Oe=Object.getOwnPropertyNames;var Me=Object.getPrototypeOf,je=Object.prototype.hasOwnProperty;var Xe=(S,b)=>()=>(b||S((b={exports:{}}).exports,b),b.exports);var Be=(S,b,g,h)=>{if(b&&typeof b=="object"||typeof b=="function")for(let f of Oe(b))!je.call(S,f)&&f!==g&&re(S,f,{get:()=>b[f],enumerable:!(h=De(b,f))||h.enumerable});return S};var Ue=(S,b,g)=>(g=S!=null?Re(Me(S)):{},Be(b||!S||!S.__esModule?re(g,"default",{value:S,enumerable:!0}):g,S));var se=Xe((D,V)=>{(function(S,b){if(typeof D=="object"&&typeof V=="object")V.exports=b();else if(typeof define=="function"&&define.amd)define([],b);else{var g=b();for(var h in g)(typeof D=="object"?D:S)[h]=g[h]}})(window,function(){return function(S){var b={};function g(h){if(b[h])return b[h].exports;var f=b[h]={i:h,l:!1,exports:{}};return S[h].call(f.exports,f,f.exports,g),f.l=!0,f.exports}return g.m=S,g.c=b,g.d=function(h,f,F){g.o(h,f)||Object.defineProperty(h,f,{enumerable:!0,get:F})},g.r=function(h){typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(h,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(h,"__esModule",{value:!0})},g.t=function(h,f){if(1&f&&(h=g(h)),8&f||4&f&&typeof h=="object"&&h&&h.__esModule)return h;var F=Object.create(null);if(g.r(F),Object.defineProperty(F,"default",{enumerable:!0,value:h}),2&f&&typeof h!="string")for(var P in h)g.d(F,P,function(T){return h[T]}.bind(null,P));return F},g.n=function(h){var f=h&&h.__esModule?function(){return h.default}:function(){return h};return g.d(f,"a",f),f},g.o=function(h,f){return Object.prototype.hasOwnProperty.call(h,f)},g.p="",g(g.s=0)}([function(S,b,g){"use strict";g.r(b);var h,f="fslightbox-",F="".concat(f,"styles"),P="".concat(f,"cursor-grabbing"),T="".concat(f,"full-dimension"),k="".concat(f,"flex-centered"),_="".concat(f,"open"),Y="".concat(f,"transform-transition"),O="".concat(f,"absoluted"),J="".concat(f,"slide-btn"),G="".concat(J,"-container"),M="".concat(f,"fade-in"),j="".concat(f,"fade-out"),H=M+"-strong",$=j+"-strong",ae="".concat(f,"opacity-"),ce="".concat(ae,"1"),W="".concat(f,"source");function K(t){return(K=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(t)}function le(t){var e=t.stageIndexes,o=t.core.stageManager,r=t.props.sources.length-1;o.getPreviousSlideIndex=function(){return e.current===0?r:e.current-1},o.getNextSlideIndex=function(){return e.current===r?0:e.current+1},o.updateStageIndexes=r===0?function(){}:r===1?function(){e.current===0?(e.next=1,delete e.previous):(e.previous=0,delete e.next)}:function(){e.previous=o.getPreviousSlideIndex(),e.next=o.getNextSlideIndex()},o.i=r<=2?function(){return!0}:function(n){var i=e.current;if(i===0&&n===r||i===r&&n===0)return!0;var a=i-n;return a===-1||a===0||a===1}}(typeof document>"u"?"undefined":K(document))==="object"&&((h=document.createElement("style")).className=F,h.appendChild(document.createTextNode(".fslightbox-absoluted{position:absolute;top:0;left:0}.fslightbox-fade-in{animation:fslightbox-fade-in .3s cubic-bezier(0,0,.7,1)}.fslightbox-fade-out{animation:fslightbox-fade-out .3s ease}.fslightbox-fade-in-strong{animation:fslightbox-fade-in-strong .3s cubic-bezier(0,0,.7,1)}.fslightbox-fade-out-strong{animation:fslightbox-fade-out-strong .3s ease}@keyframes fslightbox-fade-in{from{opacity:.65}to{opacity:1}}@keyframes fslightbox-fade-out{from{opacity:.35}to{opacity:0}}@keyframes fslightbox-fade-in-strong{from{opacity:.3}to{opacity:1}}@keyframes fslightbox-fade-out-strong{from{opacity:1}to{opacity:0}}.fslightbox-cursor-grabbing{cursor:grabbing}.fslightbox-full-dimension{width:100%;height:100%}.fslightbox-open{overflow:hidden;height:100%}.fslightbox-flex-centered{display:flex;justify-content:center;align-items:center}.fslightbox-opacity-0{opacity:0!important}.fslightbox-opacity-1{opacity:1!important}.fslightbox-scrollbarfix{padding-right:17px}.fslightbox-transform-transition{transition:transform .3s}.fslightbox-container{font-family:Arial,sans-serif;position:fixed;top:0;left:0;background:linear-gradient(rgba(30,30,30,.9),#000 1810%);touch-action:pinch-zoom;z-index:1000000000;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.fslightbox-container *{box-sizing:border-box}.fslightbox-svg-path{transition:fill .15s ease;fill:#ddd}.fslightbox-nav{height:45px;width:100%;position:absolute;top:0;left:0}.fslightbox-slide-number-container{display:flex;justify-content:center;align-items:center;position:relative;height:100%;font-size:15px;color:#d7d7d7;z-index:0;max-width:55px;text-align:left}.fslightbox-slide-number-container .fslightbox-flex-centered{height:100%}.fslightbox-slash{display:block;margin:0 5px;width:1px;height:12px;transform:rotate(15deg);background:#fff}.fslightbox-toolbar{position:absolute;z-index:3;right:0;top:0;height:100%;display:flex;background:rgba(35,35,35,.65)}.fslightbox-toolbar-button{height:100%;width:45px;cursor:pointer}.fslightbox-toolbar-button:hover .fslightbox-svg-path{fill:#fff}.fslightbox-slide-btn-container{display:flex;align-items:center;padding:12px 12px 12px 6px;position:absolute;top:50%;cursor:pointer;z-index:3;transform:translateY(-50%)}@media (min-width:476px){.fslightbox-slide-btn-container{padding:22px 22px 22px 6px}}@media (min-width:768px){.fslightbox-slide-btn-container{padding:30px 30px 30px 6px}}.fslightbox-slide-btn-container:hover .fslightbox-svg-path{fill:#f1f1f1}.fslightbox-slide-btn{padding:9px;font-size:26px;background:rgba(35,35,35,.65)}@media (min-width:768px){.fslightbox-slide-btn{padding:10px}}@media (min-width:1600px){.fslightbox-slide-btn{padding:11px}}.fslightbox-slide-btn-container-previous{left:0}@media (max-width:475.99px){.fslightbox-slide-btn-container-previous{padding-left:3px}}.fslightbox-slide-btn-container-next{right:0;padding-left:12px;padding-right:3px}@media (min-width:476px){.fslightbox-slide-btn-container-next{padding-left:22px}}@media (min-width:768px){.fslightbox-slide-btn-container-next{padding-left:30px}}@media (min-width:476px){.fslightbox-slide-btn-container-next{padding-right:6px}}.fslightbox-down-event-detector{position:absolute;z-index:1}.fslightbox-slide-swiping-hoverer{z-index:4}.fslightbox-invalid-file-wrapper{font-size:22px;color:#eaebeb;margin:auto}.fslightbox-video{object-fit:cover}.fslightbox-youtube-iframe{border:0}.fslightboxl{display:block;margin:auto;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:67px;height:67px}.fslightboxl div{box-sizing:border-box;display:block;position:absolute;width:54px;height:54px;margin:6px;border:5px solid;border-color:#999 transparent transparent transparent;border-radius:50%;animation:fslightboxl 1.2s cubic-bezier(.5,0,.5,1) infinite}.fslightboxl div:nth-child(1){animation-delay:-.45s}.fslightboxl div:nth-child(2){animation-delay:-.3s}.fslightboxl div:nth-child(3){animation-delay:-.15s}@keyframes fslightboxl{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.fslightbox-source{position:relative;z-index:2;opacity:0}")),document.head.appendChild(h));function ue(t){var e,o=t.props,r=0,n={};this.getSourceTypeFromLocalStorageByUrl=function(a){return e[a]?e[a]:i(a)},this.handleReceivedSourceTypeForUrl=function(a,c){if(n[c]===!1&&(r--,a!=="invalid"?n[c]=a:delete n[c],r===0)){(function(s,l){for(var u in l)s[u]=l[u]})(e,n);try{localStorage.setItem("fslightbox-types",JSON.stringify(e))}catch{}}};var i=function(a){r++,n[a]=!1};if(o.disableLocalStorage)this.getSourceTypeFromLocalStorageByUrl=function(){},this.handleReceivedSourceTypeForUrl=function(){};else{try{e=JSON.parse(localStorage.getItem("fslightbox-types"))}catch{}e||(e={},this.getSourceTypeFromLocalStorageByUrl=i)}}function de(t,e,o,r){var n=t.data,i=t.elements.sources,a=o/r,c=0;this.adjustSize=function(){if((c=n.maxSourceWidth/a)n.maxSourceHeight?n.maxSourceHeight:r,s()};var s=function(){i[e].style.width=c*a+"px",i[e].style.height=c+"px"}}function fe(t,e){var o=this,r=t.collections.sourceSizers,n=t.elements,i=n.sourceAnimationWrappers,a=n.sources,c=t.isl,s=t.resolve;function l(u,d){r[e]=s(de,[e,u,d]),r[e].adjustSize()}this.runActions=function(u,d){c[e]=!0,a[e].classList.add(ce),i[e].classList.add(H),i[e].removeChild(i[e].firstChild),l(u,d),o.runActions=l}}function pe(t,e){var o,r=this,n=t.elements.sources,i=t.props,a=(0,t.resolve)(fe,[e]);this.handleImageLoad=function(c){var s=c.target,l=s.naturalWidth,u=s.naturalHeight;a.runActions(l,u)},this.handleVideoLoad=function(c){var s=c.target,l=s.videoWidth,u=s.videoHeight;o=!0,a.runActions(l,u)},this.handleNotMetaDatedVideoLoad=function(){o||r.handleYoutubeLoad()},this.handleYoutubeLoad=function(){var c=1920,s=1080;i.maxYoutubeDimensions&&(c=i.maxYoutubeDimensions.width,s=i.maxYoutubeDimensions.height),a.runActions(c,s)},this.handleCustomLoad=function(){var c=n[e],s=c.offsetWidth,l=c.offsetHeight;s&&l?a.runActions(s,l):setTimeout(r.handleCustomLoad)}}function R(t,e,o){var r=t.elements.sources,n=t.props.customClasses,i=n[e]?n[e]:"";r[e].className=o+" "+i}function X(t,e){var o=t.elements.sources,r=t.props.customAttributes;for(var n in r[e])o[e].setAttribute(n,r[e][n])}function he(t,e){var o=t.collections.sourceLoadHandlers,r=t.elements,n=r.sources,i=r.sourceAnimationWrappers,a=t.props.sources;n[e]=document.createElement("img"),R(t,e,W),n[e].src=a[e],n[e].onload=o[e].handleImageLoad,X(t,e),i[e].appendChild(n[e])}function me(t,e){var o=t.collections.sourceLoadHandlers,r=t.elements,n=r.sources,i=r.sourceAnimationWrappers,a=t.props,c=a.sources,s=a.videosPosters;n[e]=document.createElement("video"),R(t,e,W),n[e].src=c[e],n[e].onloadedmetadata=function(u){o[e].handleVideoLoad(u)},n[e].controls=!0,X(t,e),s[e]&&(n[e].poster=s[e]);var l=document.createElement("source");l.src=c[e],n[e].appendChild(l),setTimeout(o[e].handleNotMetaDatedVideoLoad,3e3),i[e].appendChild(n[e])}function ge(t,e){var o=t.collections.sourceLoadHandlers,r=t.elements,n=r.sources,i=r.sourceAnimationWrappers,a=t.props.sources;n[e]=document.createElement("iframe"),R(t,e,"".concat(W," ").concat(f,"youtube-iframe"));var c=a[e],s=c.split("?")[1];n[e].src="https://www.youtube.com/embed/".concat(c.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/)[2],"?").concat(s||""),n[e].allowFullscreen=!0,X(t,e),i[e].appendChild(n[e]),o[e].handleYoutubeLoad()}function ve(t,e){var o=t.collections.sourceLoadHandlers,r=t.elements,n=r.sources,i=r.sourceAnimationWrappers,a=t.props.sources;n[e]=a[e],R(t,e,"".concat(n[e].className," ").concat(W)),i[e].appendChild(n[e]),o[e].handleCustomLoad()}function be(t,e){var o=t.elements,r=o.sources,n=o.sourceAnimationWrappers;t.props.sources,r[e]=document.createElement("div"),r[e].className="".concat(f,"invalid-file-wrapper ").concat(k),r[e].innerHTML="Invalid source",n[e].classList.add(H),n[e].removeChild(n[e].firstChild),n[e].appendChild(r[e])}function xe(t){var e=t.collections,o=e.sourceLoadHandlers,r=e.sourcesRenderFunctions,n=t.core.sourceDisplayFacade,i=t.resolve;this.runActionsForSourceTypeAndIndex=function(a,c){var s;switch(a!=="invalid"&&(o[c]=i(pe,[c])),a){case"image":s=he;break;case"video":s=me;break;case"youtube":s=ge;break;case"custom":s=ve;break;default:s=be}r[c]=function(){return s(t,c)},n.displaySourcesWhichShouldBeDisplayed()}}function ye(){var t,e,o,r={isUrlYoutubeOne:function(i){var a=document.createElement("a");return a.href=i,a.hostname==="www.youtube.com"||a.hostname==="youtu.be"},getTypeFromResponseContentType:function(i){return i.slice(0,i.indexOf("/"))}};function n(){if(o.readyState!==4){if(o.readyState===2){var i;switch(r.getTypeFromResponseContentType(o.getResponseHeader("content-type"))){case"image":i="image";break;case"video":i="video";break;default:i="invalid"}o.onreadystatechange=null,o.abort(),e(i)}}else e("invalid")}this.setUrlToCheck=function(i){t=i},this.getSourceType=function(i){if(r.isUrlYoutubeOne(t))return i("youtube");e=i,(o=new XMLHttpRequest).onreadystatechange=n,o.open("GET",t,!0),o.send()}}function we(t,e,o){var r=t.props,n=r.types,i=r.type,a=r.sources,c=t.resolve;this.getTypeSetByClientForIndex=function(s){var l;return n&&n[s]?l=n[s]:i&&(l=i),l},this.retrieveTypeWithXhrForIndex=function(s){var l=c(ye);l.setUrlToCheck(a[s]),l.getSourceType(function(u){e.handleReceivedSourceTypeForUrl(u,a[s]),o.runActionsForSourceTypeAndIndex(u,s)})}}function Se(t,e){var o=t.core.stageManager,r=t.elements,n=r.smw,i=r.sourceWrappersContainer,a=t.props,c=0,s=document.createElement("div");function l(d){s.style.transform="translateX(".concat(d+c,"px)"),c=0}function u(){return(1+a.slideDistance)*innerWidth}s.className="".concat(O," ").concat(T," ").concat(k),s.s=function(){s.style.display="flex"},s.h=function(){s.style.display="none"},s.a=function(){s.classList.add(Y)},s.d=function(){s.classList.remove(Y)},s.n=function(){s.style.removeProperty("transform")},s.v=function(d){return c=d,s},s.ne=function(){l(-u())},s.z=function(){l(0)},s.p=function(){l(u())},o.i(e)||s.h(),n[e]=s,i.appendChild(s),function(d,p){var w=d.elements,x=w.smw,L=w.sourceAnimationWrappers,m=document.createElement("div"),y=document.createElement("div");y.className="fslightboxl";for(var v=0;v<3;v++){var E=document.createElement("div");y.appendChild(E)}m.appendChild(y),x[p].appendChild(m),L[p]=m}(t,e)}function B(t,e,o,r){var n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttributeNS(null,"width",e),n.setAttributeNS(null,"height",e),n.setAttributeNS(null,"viewBox",o);var i=document.createElementNS("http://www.w3.org/2000/svg","path");return i.setAttributeNS(null,"class","".concat(f,"svg-path")),i.setAttributeNS(null,"d",r),n.appendChild(i),t.appendChild(n),n}function Q(t,e){var o=document.createElement("div");return o.className="".concat(f,"toolbar-button ").concat(k),o.title=e,t.appendChild(o),o}function Le(t,e){var o=document.createElement("div");o.className="".concat(f,"toolbar"),e.appendChild(o),function(r,n){var i=r.componentsServices,a=r.data,c=r.fs,s="M4.5 11H3v4h4v-1.5H4.5V11zM3 7h1.5V4.5H7V3H3v4zm10.5 6.5H11V15h4v-4h-1.5v2.5zM11 3v1.5h2.5V7H15V3h-4z",l=Q(n);l.title="Enter fullscreen";var u=B(l,"20px","0 0 18 18",s);i.ofs=function(){a.ifs=!0,l.title="Exit fullscreen",u.setAttributeNS(null,"width","24px"),u.setAttributeNS(null,"height","24px"),u.setAttributeNS(null,"viewBox","0 0 950 1024"),u.firstChild.setAttributeNS(null,"d","M682 342h128v84h-212v-212h84v128zM598 810v-212h212v84h-128v128h-84zM342 342v-128h84v212h-212v-84h128zM214 682v-84h212v212h-84v-128h-128z")},i.xfs=function(){a.ifs=!1,l.title="Enter fullscreen",u.setAttributeNS(null,"width","20px"),u.setAttributeNS(null,"height","20px"),u.setAttributeNS(null,"viewBox","0 0 18 18"),u.firstChild.setAttributeNS(null,"d",s)},l.onclick=c.t}(t,o),function(r,n){var i=Q(n,"Close");i.onclick=r.core.lightboxCloser.closeLightbox,B(i,"20px","0 0 24 24","M 4.7070312 3.2929688 L 3.2929688 4.7070312 L 10.585938 12 L 3.2929688 19.292969 L 4.7070312 20.707031 L 12 13.414062 L 19.292969 20.707031 L 20.707031 19.292969 L 13.414062 12 L 20.707031 4.7070312 L 19.292969 3.2929688 L 12 10.585938 L 4.7070312 3.2929688 z")}(t,o)}function Ae(t){var e=t.props.sources,o=t.elements.container,r=document.createElement("div");r.className="".concat(f,"nav"),o.appendChild(r),Le(t,r),e.length>1&&function(n,i){var a=n.componentsServices,c=n.props.sources,s=(n.stageIndexes,document.createElement("div"));s.className="".concat(f,"slide-number-container");var l=document.createElement("div");l.className=k;var u=document.createElement("span");a.setSlideNumber=function(w){return u.innerHTML=w};var d=document.createElement("span");d.className="".concat(f,"slash");var p=document.createElement("div");p.innerHTML=c.length,s.appendChild(l),l.appendChild(u),l.appendChild(d),l.appendChild(p),i.appendChild(s),setTimeout(function(){l.offsetWidth>55&&(s.style.justifyContent="flex-start")})}(t,r)}function Z(t,e,o,r){var n=t.elements.container,i=o.charAt(0).toUpperCase()+o.slice(1),a=document.createElement("div");a.className="".concat(G," ").concat(G,"-").concat(o),a.title="".concat(i," slide"),a.onclick=e,function(c,s){var l=document.createElement("div");l.className="".concat(J," ").concat(k),B(l,"20px","0 0 20 20",s),c.appendChild(l)}(a,r),n.appendChild(a)}function Ce(t){var e=t.core,o=e.lightboxCloser,r=e.slideChangeFacade,n=t.fs;this.listener=function(i){switch(i.key){case"Escape":o.closeLightbox();break;case"ArrowLeft":r.changeToPrevious();break;case"ArrowRight":r.changeToNext();break;case"F11":i.preventDefault(),n.t()}}}function Ee(t){var e=t.elements,o=t.sourcePointerProps,r=t.stageIndexes;function n(i,a){e.smw[i].v(o.swipedX)[a]()}this.runActionsForEvent=function(i){var a,c,s;e.container.contains(e.slideSwipingHoverer)||e.container.appendChild(e.slideSwipingHoverer),a=e.container,c=P,(s=a.classList).contains(c)||s.add(c),o.swipedX=i.screenX-o.downScreenX;var l=r.previous,u=r.next;n(r.current,"z"),l!==void 0&&o.swipedX>0?n(l,"ne"):u!==void 0&&o.swipedX<0&&n(u,"p")}}function Fe(t){var e=t.props.sources,o=t.resolve,r=t.sourcePointerProps,n=o(Ee);e.length===1?this.listener=function(){r.swipedX=1}:this.listener=function(i){r.isPointering&&n.runActionsForEvent(i)}}function Ie(t){var e=t.core.slideIndexChanger,o=t.elements.smw,r=t.stageIndexes,n=t.sws;function i(c){var s=o[r.current];s.a(),s[c]()}function a(c,s){c!==void 0&&(o[c].s(),o[c][s]())}this.runPositiveSwipedXActions=function(){var c=r.previous;if(c===void 0)i("z");else{i("p");var s=r.next;e.changeTo(c);var l=r.previous;n.d(l),n.b(s),i("z"),a(l,"ne")}},this.runNegativeSwipedXActions=function(){var c=r.next;if(c===void 0)i("z");else{i("ne");var s=r.previous;e.changeTo(c);var l=r.next;n.d(l),n.b(s),i("z"),a(l,"p")}}}function ee(t,e){t.contains(e)&&t.removeChild(e)}function Te(t){var e=t.core.lightboxCloser,o=t.elements,r=t.resolve,n=t.sourcePointerProps,i=r(Ie);this.runNoSwipeActions=function(){ee(o.container,o.slideSwipingHoverer),n.isSourceDownEventTarget||e.closeLightbox(),n.isPointering=!1},this.runActions=function(){n.swipedX>0?i.runPositiveSwipedXActions():i.runNegativeSwipedXActions(),ee(o.container,o.slideSwipingHoverer),o.container.classList.remove(P),n.isPointering=!1}}function Ne(t){var e=t.resolve,o=t.sourcePointerProps,r=e(Te);this.listener=function(){o.isPointering&&(o.swipedX?r.runActions():r.runNoSwipeActions())}}function ze(t){var e=this,o=t.core,r=o.eventsDispatcher,n=o.globalEventsController,i=o.scrollbarRecompensor,a=t.data,c=t.elements,s=t.fs,l=t.props,u=t.sourcePointerProps;this.isLightboxFadingOut=!1,this.runActions=function(){e.isLightboxFadingOut=!0,c.container.classList.add($),n.removeListeners(),l.exitFullscreenOnClose&&a.ifs&&s.x(),setTimeout(function(){e.isLightboxFadingOut=!1,u.isPointering=!1,c.container.classList.remove($),document.documentElement.classList.remove(_),i.removeRecompense(),document.body.removeChild(c.container),r.dispatch("onClose")},270)}}function U(t,e){var o=t.classList;o.contains(e)&&o.remove(e)}function Pe(t){var e,o,r;o=(e=t).core.eventsDispatcher,r=e.props,o.dispatch=function(n){r[n]&&r[n]()},function(n){var i=n.componentsServices,a=n.data,c=n.fs,s=["fullscreenchange","webkitfullscreenchange","mozfullscreenchange","MSFullscreenChange"];function l(d){for(var p=0;pinnerHeight&&(document.body.style.marginRight=i.scrollbarWidth+"px")}a.addRecompense=function(){document.readyState==="complete"?c():addEventListener("load",function(){c(),a.addRecompense=c})},a.removeRecompense=function(){document.body.style.removeProperty("margin-right")}}(t),function(n){var i=n.core,a=i.slideChangeFacade,c=i.slideIndexChanger,s=i.stageManager;n.props.sources.length>1?(a.changeToPrevious=function(){c.jumpTo(s.getPreviousSlideIndex())},a.changeToNext=function(){c.jumpTo(s.getNextSlideIndex())}):(a.changeToPrevious=function(){},a.changeToNext=function(){})}(t),function(n){var i=n.componentsServices,a=n.core,c=a.slideIndexChanger,s=a.sourceDisplayFacade,l=a.stageManager,u=n.elements,d=u.smw,p=u.sourceAnimationWrappers,w=n.isl,x=n.stageIndexes,L=n.sws;c.changeTo=function(m){x.current=m,l.updateStageIndexes(),i.setSlideNumber(m+1),s.displaySourcesWhichShouldBeDisplayed()},c.jumpTo=function(m){var y=x.previous,v=x.current,E=x.next,A=w[v],I=w[m];c.changeTo(m);for(var C=0;C1&&(L=(x=t).core.slideChangeFacade,Z(x,L.changeToPrevious,"previous","M18.271,9.212H3.615l4.184-4.184c0.306-0.306,0.306-0.801,0-1.107c-0.306-0.306-0.801-0.306-1.107,0L1.21,9.403C1.194,9.417,1.174,9.421,1.158,9.437c-0.181,0.181-0.242,0.425-0.209,0.66c0.005,0.038,0.012,0.071,0.022,0.109c0.028,0.098,0.075,0.188,0.142,0.271c0.021,0.026,0.021,0.061,0.045,0.085c0.015,0.016,0.034,0.02,0.05,0.033l5.484,5.483c0.306,0.307,0.801,0.307,1.107,0c0.306-0.305,0.306-0.801,0-1.105l-4.184-4.185h14.656c0.436,0,0.788-0.353,0.788-0.788S18.707,9.212,18.271,9.212z"),Z(x,L.changeToNext,"next","M1.729,9.212h14.656l-4.184-4.184c-0.307-0.306-0.307-0.801,0-1.107c0.305-0.306,0.801-0.306,1.106,0l5.481,5.482c0.018,0.014,0.037,0.019,0.053,0.034c0.181,0.181,0.242,0.425,0.209,0.66c-0.004,0.038-0.012,0.071-0.021,0.109c-0.028,0.098-0.075,0.188-0.143,0.271c-0.021,0.026-0.021,0.061-0.045,0.085c-0.015,0.016-0.034,0.02-0.051,0.033l-5.483,5.483c-0.306,0.307-0.802,0.307-1.106,0c-0.307-0.305-0.307-0.801,0-1.105l4.184-4.185H1.729c-0.436,0-0.788-0.353-0.788-0.788S1.293,9.212,1.729,9.212z")),function(m){for(var y=m.props.sources,v=m.resolve,E=v(ue),A=v(xe),I=v(we,[E,A]),C=0;C0&&arguments[0]!==void 0?arguments[0]:0,L=d.previous,m=d.current,y=d.next;d.current=x,l.i||le(t),c.updateStageIndexes(),l.i?(p.c(),p.a(),p.b(L),p.b(m),p.b(y),r.dispatch("onShow")):w(),a.displaySourcesWhichShouldBeDisplayed(),e.setSlideNumber(x+1),document.body.appendChild(u.container),document.documentElement.classList.add(_),i.addRecompense(),n.attachListeners(),s.runActions(),u.smw[d.current].n(),r.dispatch("onOpen")}}function te(t,e,o){return(te=He()?Reflect.construct.bind():function(r,n,i){var a=[null];a.push.apply(a,n);var c=new(Function.bind.apply(r,a));return i&&ne(c,i.prototype),c}).apply(null,arguments)}function He(){if(typeof Reflect>"u"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch{return!1}}function ne(t,e){return(ne=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(o,r){return o.__proto__=r,o})(t,e)}function We(t){return function(e){if(Array.isArray(e))return q(e)}(t)||function(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}(t)||function(e,o){if(e){if(typeof e=="string")return q(e,o);var r=Object.prototype.toString.call(e).slice(8,-1);if(r==="Object"&&e.constructor&&(r=e.constructor.name),r==="Map"||r==="Set")return Array.from(e);if(r==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return q(e,o)}}(t)||function(){throw new TypeError(`Invalid attempt to spread non-iterable instance.
2 | In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}()}function q(t,e){(e==null||e>t.length)&&(e=t.length);for(var o=0,r=new Array(e);o1&&arguments[1]!==void 0?arguments[1]:[];return o.unshift(t),te(e,We(o))},this.collections={sourceLoadHandlers:[],sourcesRenderFunctions:[],sourceSizers:[]},this.core={eventsDispatcher:{},globalEventsController:{},lightboxCloser:{},lightboxUpdater:{},scrollbarRecompensor:{},slideChangeFacade:{},slideIndexChanger:{},sourcesPointerDown:{},sourceDisplayFacade:{},stageManager:{},windowResizeActioner:{}},this.fs={},this.sws={},ke(this),this.close=function(){return t.core.lightboxCloser.closeLightbox()}},window.fsLightboxInstances={},oe(),window.refreshFsLightbox=function(){for(var t in fsLightboxInstances){var e=fsLightboxInstances[t].props;fsLightboxInstances[t]=new FsLightbox,fsLightboxInstances[t].props=e,fsLightboxInstances[t].props.sources=[],fsLightboxInstances[t].elements.a=[]}oe()}}])})});var Ve=Ue(se(),1);
3 |
--------------------------------------------------------------------------------