Code Monkey home page Code Monkey logo

filament-curator's Introduction

Filament Curator

Latest Version on Packagist Total Downloads

A media picker/manager plugin for Filament Admin.

Warning This package does not work with Spatie Media Library.

curator-og

Installation

You can install the package via composer then run the installation command:

composer require awcodes/filament-curator
php artisan curator:install

If you are using the stand-alone forms package then you will need to include the Curator modal in your layout file, typically you would place this, before the closing body tag.

<x-curator::modals.modal />

In an effort to align with Filament's theming methodology you will need to use a custom theme to use this plugin.

Note If you have not set up a custom theme and are using a Panel follow the instructions in the Filament Docs first. The following applies to both the Panels Package and the standalone Forms package.

You will also need to add cropper.js.

npm install -D cropperjs
  1. Import the plugin's stylesheet and cropperjs' stylesheet into your theme's css file.
@import '<path-to-node-modules>/cropperjs/dist/cropper.css';
@import '<path-to-vendor>/awcodes/filament-curator/resources/css/plugin.css';
  1. Add the plugin's views to your tailwind.config.js file.
content: [
    './vendor/awcodes/filament-curator/resources/**/*.blade.php',
]

Upgrading

If you are upgrading from 2.x to 3.x you will also need to run:

php artisan curator:upgrade

This will update Curator's database schema and create a backup of your media table that can be deleted after upgrade should you choose to do so.

Additional Steps

  1. CurationPreset will need to be updated to the new format.
  2. GliderFallback will need to be updated to the new format.
  3. Custom Glide Server Factories will need to be updated to use the new format.

Usage

Global Settings

Global settings can be managed through the plugin's config file. You can publish the config file using the following:

php artisan vendor:publish --tag="curator-config"

With Filament Panels

If you are using Filament Panels you will need to add the Plugin to your Panel's configuration. This will register the plugin's resources with the Panel. All methods are optional, and will be read from the config file if not provided.

public function panel(Panel $panel): Panel
{
    return $panel
        ->plugins([
            \Awcodes\Curator\CuratorPlugin::make()
                ->label('Media')
                ->pluralLabel('Media')
                ->navigationIcon('heroicon-o-photo')
                ->navigationGroup('Content')
                ->navigationSort(3)
                ->navigationCountBadge()
                ->registerNavigation(false)
                ->defaultListView('grid' || 'list')
                ->resource(\App\Filament\Resources\CustomMediaResource::class)
        ]);
}

Curator Picker Field

Include the CuratorPicker field in your forms to trigger the modal and either select an existing image or upload a new one. Some common methods from Filament's FileUpload component can be used to help with sizing, validation, etc. for specific instances of each CuratorPicker.

use Awcodes\Curator\Components\Forms\CuratorPicker;

CuratorPicker::make(string $fieldName)
    ->label(string $customLabel)
    ->buttonLabel(string | Htmlable | Closure $buttonLabel)
    ->color('primary|secondary|success|danger') // defaults to primary
    ->outlined(true|false) // defaults to true
    ->size('sm|md|lg') // defaults to md
    ->constrained(true|false) // defaults to false (forces image to fit inside the preview area)
    ->pathGenerator(DatePathGenerator::class|UserPathGenerator::class) // see path generators below
    ->lazyLoad(bool | Closure $condition) // defaults to true
    ->listDisplay(bool | Closure $condition) // defaults to true
    ->tenantAware(bool | Closure $condition) // defaults to true
    ->defaultPanelSort(string | Closure $direction) // defaults to 'desc'
    // see https://filamentphp.com/docs/2.x/forms/fields#file-upload for more information about the following methods
    ->preserveFilenames()
    ->maxWidth()
    ->minSize()
    ->maxSize()
    ->rules()
    ->acceptedFileTypes()
    ->disk()
    ->visibility()
    ->directory()
    ->imageCropAspectRatio()
    ->imageResizeTargetWidth()
    ->imageResizeTargetHeight()
    ->multiple() // required if using a relationship with multiple media
    ->relationship(string $relationshipName, string 'titleColumnName')
    ->orderColumn('order') // only necessary to rename the order column if using a relationship with multiple media

Relationships

Single

Form component

CuratorPicker::make('featured_image_id')
    ->relationship('featured_image', 'id'),

Model

use Awcodes\Curator\Models\Media;

public function featuredImage(): BelongsTo
{
    return $this->belongsTo(Media::class, 'featured_image_id', 'id');
}

Multiple

Form component

CuratorPicker::make('product_picture_ids')
    ->multiple()
    ->relationship('product_pictures', 'id')
    ->orderColumn('order'), // only necessary if you need to rename the order column

Model

use Awcodes\Curator\Models\Media;

public function productPictures(): BelongsToMany
{
    return $this
        ->belongsToMany(Media::class, 'media_post', 'post_id', 'media_id')
        ->withPivot('order')
        ->orderBy('order');
}

Path Generation

By default, Curator will use the directory and disk set in the config to store your media. If you'd like to store the media in a different way Curator comes with Path Generators that can be used to modify the behavior. Just set the one you want to use globally in the config or per instance on your CuratorPicker field.

use Awcodes\Curator\View\Components\CuratorPicker;
use Awcodes\Curator\PathGenerators\DatePathGenerator;

public function register()
{
    CuratorPicker::make('image')
        ->pathGenerator(DatePathGenerator::class);
}

Available Generators

DefaultPathGenerator will save files in disk/directory.

DatePathGenerator will save files in disk/directory/Y/m/d.

UserPathGenerator will save files in disk/directory/user-auth-identifier

You are also free to use your own Path Generators by implementing the PathGenerator interface on your own classes.

use Awcodes\Curator\PathGenerators;

class CustomPathGenerator implements PathGenerator
{
    public function getPath(?string $baseDir = null): string
    {
        return ($baseDir ? $baseDir . '/' : '') . 'my/custom/path';
    }
}

Curator Column

To render your media in a table Curator comes with a CuratorColumn which has the same methods as Filament's ImageColumn.

CuratorColumn::make('featured_image')
    ->size(40)

For multiple images you can control the number of images shown, the ring size and the overlap.

CuratorColumn::make('product_pictures')
    ->ring(2) // options 0,1,2,4
    ->overlap(4) // options 0,2,3,4
    ->limit(3),

Relationships

If you are using a relationship to store your media then you will encounter n+1 issues on the column. In order to prevent this you should modify your table query to eager load the relationship.

For example when using the admin panel in your ListResource

protected function getTableQuery(): Builder
{
    return parent::getTableQuery()->with(['featured_image', 'product_pictures']);
}

Curations

Curations are a way to create custom sizes and focal points for your images.

Curation Presets

If you have a curation that you are constantly using you can create Presets which will be available in the Curation modal for easier reuse. After creating curation presets, they can be referenced by their key to output them in your blade files.

use Awcodes\Curator\Curations\CurationPreset;

class ThumbnailPreset extends CurationPreset
{
    public function getKey(): string
    {
        return 'thumbnail';
    }

    public function getLabel(): string
    {
        return 'Thumbnail';
    }

    public function getWidth(): int
    {
        return 200;
    }

    public function getHeight(): int
    {
        return 200;
    }

    public function getFormat(): string
    {
        return 'webp';
    }

    public function getQuality(): int
    {
        return 60;
    }
}

Then simply register your preset in the config.

'curation_presets' => [
    ThumbnailPreset::class,
],

You can also change which formats are available for curations by changing the curation_formats in the config file. These should be compatible with Intervention Image's encoding types.

'curation_formats' => [
    'jpg',
    'jpeg',
    'webp',
    'png',
    'avif',
],

If you wish to disable the "Curation" tab in the Media Editor you can do so by setting tabs.display_curation to false in the config file. The default is true.

'tabs' => [
    'display_curation' => false,
],

If you wish to disable the "Upload New" tab in the Media Editor you can do so by setting tabs.display_upload_new to false in the config file. The default is true.

'tabs' => [
    'display_upload_new' => false,
],

Glider Blade Component

To make it as easy as possible to output your media, Curator comes with an <x-curator-glider> blade component.

See Glide's quick reference for more information about Glide's options.

Special attributes

  • media: id (int) or model (Media) instance required
  • loading: defaults to 'lazy'
  • glide: this can be used to pass in a glide query string if you do not want to use individual attributes
  • srcset: this will output the necessary srcset with glide generated urls. Must be an array of srcset widths and requires the 'sizes' attribute to also be set.
  • force: (bool) this can be used to force glider to return a signed url and is helpful when returning urls from cloud disks. This should be used with the knowledge that it could have performance implications.
<div class="w-64 aspect-video">
    <x-curator-glider
        class="object-cover w-auto"
        :media="1"
        glide=""
        fallback=""
        :srcset="['1024w','640w']"
        sizes="(max-width: 1200px) 100vw, 1024px"
        background=""
        blur=""
        border=""
        brightness=""
        contrast=""
        crop=""
        device-pixel-ratio=""
        filter=""
        fit=""
        flip=""
        format=""
        gamma=""
        height=""
        quality=""
        orientation=""
        pixelate=""
        sharpen=""
        width=""
        watermark-path=""
        watermark-width=""
        watermark-height=""
        watermark-x-offset=""
        watermark-y-offset=""
        watermark-padding=""
        watermark-position=""
        watermark-alpha=""
    />
</div>

Glider Fallback Images

Glider allows for a fallback image to be used if the media item does not exist. This can be set by passing in the fallback attribute referencing one of your registered GliderFallbacks.

use Awcodes\Curator\Glide\GliderFallback;

class MyCustomGliderFallback extends GliderFallback
{
    public function getAlt(): string
    {
        return 'boring fallback image';
    }

    public function getHeight(): int
    {
        return 640;
    }

    public function getKey(): string
    {
        return 'card_fallback';
    }

    public function getSource(): string
    {
        return 'https://via.placeholder.com/640x420.jpg';
    }

    public function getType(): string
    {
        return 'image/jpg';
    }

    public function getWidth(): int
    {
        return 420;
    }
}

Then register your fallback in the config.

'glide' => [
    'fallbacks' => [
        MyCustomGliderFallback::class,
    ],
],

Then you can reference your fallback in the blade component.

<x-curator-glider :media="1" fallback="card_fallback"/>

Custom Glide Route

By default, Curator will use the route curator when serving images through Glide. If you want to change this you can update the glide.route_path setting in the Curator config file.

'glide' => [
    'route_path' => 'uploads',
],

Custom Glide Server

If you want to use your own Glide Server for handling served media with Glide you can implement the ServerFactory interface on your own classes and set it to the config.

use League\Glide\Responses\LaravelResponseFactory;
use League\Glide\Server;
use League\Glide\ServerFactory;

class CustomServerFactory implements Contracts\ServerFactory
{
    public function getFactory(): ServerFactory | Server
    {
        return ServerFactory::create([
            'driver' => 'imagick',
            'response' => new LaravelResponseFactory(app('request')),
            'source' => storage_path('app'),
            'source_path_prefix' => 'public',
            'cache' => storage_path('app'),
            'cache_path_prefix' => '.cache',
            'max_image_size' => 2000 * 2000,
        ]);
    }
}

Then register your server in the config.

'glide' => [
    'server' => \App\Glide\CustomServerFactory::class,
],

Curation Blade Component

To make it as easy as possible to output your curations, Curator comes with an <x-curator-curation> blade component.

Special attributes

  • media: id (int) or model (Media) instance required
<x-curator-curation :media="10" curation="thumbnail" loading="lazy"/>

Practical use case

Since curations may or may not exist for each media item it's good to use a fallback to the glider component in your blade file so images always get rendered appropriately. This also keeps you from having to create curations for every media item, only the ones where you're trying to change the focal point, etc.

@php
    $preset = new ThumbnailPreset();
@endphp

@if ($media->hasCuration('thumbnail'))
    <x-curator-curation :media="$media" curation="thumbnail"/>
@else
    <x-curator-glider
        class="object-cover w-auto"
        :media="$media"
        :width="$preset->getWidth()"
        :height="$preset->getHeight()"
    />
@endif

Custom Model

If you want to use your own model for your media you can extend Curator's Media model with your own and set it in the config.

use Awcodes\Curator\Models\Media;

class CustomMedia extends Media
{
    protected $table = 'media';
}
'model' => \App\Models\Cms\Media::class,

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.

filament-curator's People

Contributors

abbasmashaddy72 avatar archilex avatar askancy avatar atmonshi avatar awcodes avatar aymanalareqi avatar danharrin avatar dependabot[bot] avatar dmitriydalee avatar fvanzeijl avatar github-actions[bot] avatar happytodev avatar howdu avatar iraziul avatar jarkap avatar kirkbushell avatar kuragehimekurara1 avatar lakuapik avatar log1x avatar lotarbo avatar margarizaldi avatar martin-ro avatar martinmildner avatar mohamedsabil83 avatar mszabeh avatar nxgcx avatar plompd avatar shayan-yousefi avatar tlegenbayangali avatar viraljetani avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

filament-curator's Issues

Upload breaks on Images with exif Encoding Issues

Hi!
I have an issue with some images that seem to contain malformed exif data. Whenever these are uploaded, the process breaks and returns the following:

Illuminate\Database\Eloquent\JsonEncodingException: Unable to encode attribute [exif] for model [Awcodes\Curator\Models\Media] to JSON: Malformed UTF-8 characters, possibly incorrectly encoded.

Expected behavior would be that the exif data gets fixed or thrown away with a warning instead of making it impossible to upload the image.

This is one of the images that breaks the upload:
CDBCAB70-EF98-49AC-BFA0-55805C0AC383

Ver. 1.0.0+ breaks the close button for the Admin package database notifications slide-over

Like the title says, the close button (x) on the slide-over for database notifications (the bell in the top nav) becomes not clickable with Curator 1.0.0 and up installed. I rolled back to 0.7.7 and it works again on that version.

No idea why (no console errors), I found it by trial and error by removing non-default composer packages one at a time.

This is on the latest version of Filament, 2.16.52 (also tried 2.16.51 to no avail).

Curations are not getting deleted with the original media

Hey,
i have a problem with deleting media. As example if i upload an image it gets saved as:
media/ad10b2f8-d84f-4a10-9924-f65e00238f2c.jpg

A curation then gets saved as:
media/ad10b2f8-d84f-4a10-9924-f65e00238f2c/custom.jpg

If i delete the Media only the original image file gets deleted.

Builder methods in Curator facade yield editor warning

After installing package with composer require awcodes/filament-curator and php artisan curator:install, I want to change global settings (f.e. maximum size).

Tutorial suggests to use builder methods on Awcodes\Curator\Facades\Curator facade.

When I use Curator:maxSize(2000) I receive a warning from Intelephense:

image

My guess is that while docblock successfully translates facade method * @method static maxSize(int|Closure $size) into static method, it fails to recognize the real return type -- static, resulting in the warning above.

I suggest adding a second static keyword to these builder methods to imply real return type:

* @method static static maxSize(int|Closure $size)

same approach as in Carbon\Traits\Comparison

Undefined array key "disk"

When i use Curation Blade Component
<x-curator-curation :media="3" class="h-10" curation="thumbnail" />
i get this error Undefined array key "disk"

@awcodes

Can not upload pdf file

I have this error when uploading pdf file : array_walk_recursive(): Argument #1 ($array) must be of type array, null given

Inconsistent property type of \Awcodes\Curator\Components\Modals\CuratorPanel::$pathGenerator

\Awcodes\Curator\Components\Modals\CuratorPanel::$pathGenerator expects to be an instance of PathGenerator while Curator::pathGenerator() expects string. So, if I follow the documentation:

Curator::pathGenerator(DatePathGenerator::class);

throws

Cannot assign string to property Awcodes\Curator\Components\Modals\CuratorPanel::$pathGenerator of type ?Awcodes\Curator\Generators\PathGenerator

Most likely it's broken by commit

Mistake in README -> Usage

Under usage:

use FilamentCurator\Forms\MediaPicker;

should be,

use FilamentCurator\Forms\Components\MediaPicker;

sizeForHumans returning incorrect unit value

The current method attempts to divide the model's size attribute directly. This small adjustment returns the correct unit:

        public function sizeForHumans(int $precision = 1): string
	{
		$units = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
		$size = $this->size;
		for ($i = 0; $size > 1024; $i++) {
			$size /= 1024;
		}

		return round($size, $precision) . " " . $units[$i];
	}

Instalation

I had a small trouble because I have no link to the media directory in my public folder.

It could be interesting (or not ?) to add in the install documentation to run the command

php artisan storage:link

I'm using S3 and I'm getting an error that says "Image source not readable."

Thank you for your great library.

I'm encountering an error and have searched through issues and discussions, but I couldn't find a solution. Therefore, I'm creating a new issue.

Steps taken:

  1. Installed a new project.
  2. Installed Filament.
  3. Installed Filament-curator.
  4. Configured the file system using S3.
  5. Went to the Media menu,
  • selected "New Media,"
  • browsed for the image (upload was successful and the file is in the livewire-tmp folder on S3),
  • clicked the "Create" button, but received the "Image source not readable" error.

Can you please assist me in finding a solution? I'm currently at a standstill.

Screenshot 2023-04-02 at 01 00 27

Thumbnail images for non-image mimetypes

An option for later.
Currently the thumbnail is broken if the media is anything but an image, since it's attempting to load it into the src="" attribute.

Detect mimetype and return a standard icon representing the file.

Is croping possible?

Hello,

I would like to crop some images when i generate different sizes instead of resize, is croping possible with this package?

maybe an optinal cropping when width and height are filled?

thank you

fitContent does not exist

Hello. Docs says that i can use fitContent, but method doesn't exist.
Also without fit some images look bad
image
and this is original image
image

Image preview not working on media page

Hello,

the image preview on the media page is not working on my installation.

image

I temporarily fixed it by setting "source_path_prefix" in /src/Http/Controllers/MediaController.php(line 42) to an empty string.

Not working:
$server = ServerFactory::create([ 'response' => new LaravelResponseFactory(app('request')), 'source' => $filesystem->getDriver(), 'source_path_prefix' => 'public', 'cache' => $filesystem->getDriver(), 'cache_path_prefix' => '.cache', 'base_url' => 'curator', ]);

Working:
$server = ServerFactory::create([ 'response' => new LaravelResponseFactory(app('request')), 'source' => $filesystem->getDriver(), 'source_path_prefix' => '', 'cache' => $filesystem->getDriver(), 'cache_path_prefix' => '.cache', 'base_url' => 'curator', ]);

It would be nice if this can be set with a setting.

I am using version 2.0.9.

Use config for acceptedFileTypes in MediaResource

The config file has an option to set the acceptedFileTypes but the component still sets the types explicitly within MediaResource. Possible fix:

Update:

->acceptedFileTypes([
    "image/jpeg",
    "image/png",
    "image/webp",
    "image/svg+xml",
    "application/pdf",
])

To:

->acceptedFileTypes(config('filament-curator.accepted_file_types'))

insert video from filament-curator

How can I insert video from media curator?
I added the video to the curator media but when I try to select and import the video from curator, the editor doesn't show anything.

Could not install on new laravel 10

When I try to install this package using the new laravel 10
it shows this message :
Problem 1
- Root composer.json requires awcodes/filament-curator 2.1.2 -> satisfiable by awcodes/filament-curator[v2.1.2].
- awcodes/filament-curator v2.1.2 requires illuminate/contracts ^9.0 -> found illuminate/contracts[v9.0.0, ..., v9.52.0] but these were not loaded, likely because it conflicts with another require.

Generate webp thumbnails

Hi! Can I generate webp thumbnails when uploading pictures? And can I prevent the thumbnail size growing if the original picture size is smaller than the thumbnail size?
Sorry for my English, translated it through DeepL))

generateThumbs being called on non-image types

Within the created closure on the Media model, self::generateThumbs($media) is being called even if the mimetype is not an image (ie. PDF). This causes a fatal error with Intervention. Possible fix:

                static::created(function (Media $media) {
			$media->refresh();
			if (str($media->type)->contains("image")) {
				self::generateThumbs($media);
			}
		});

ERROR: Unable to retrieve the visibility for file at location...

Hi @awcodes,

There seems to be an issue when deleting files from the library (FILESYSTEM_DISK=public).

Could you perhaps shed some light on this? Perhaps I'm missing some configuration?

Here's the error and stack trace I get:

ERROR: Unable to retrieve the visibility for file at location: media/96f646eb-9f3a-4267-a0d5-50621a185a52.jpg.  {"userId":1,"exception":"[object] (League\\Flysystem\\UnableToRetrieveMetadata(code: 0): Unable to retrieve the visibility for file at location: media/96f646eb-9f3a-4267-a0d5-50621a185a52.jpg.  at ...\\vendor\\league\\flysystem\\src\\UnableToRetrieveMetadata.php:49)
[stacktrace]
#0 ...\\vendor\\league\\flysystem\\src\\UnableToRetrieveMetadata.php(34): League\\Flysystem\\UnableToRetrieveMetadata::create('media/96f646eb-...', 'visibility', '', NULL)
#1 ...\\vendor\\league\\flysystem-local\\LocalFilesystemAdapter.php(369): League\\Flysystem\\UnableToRetrieveMetadata::visibility('media/96f646eb-...', '')
#2 ...\\vendor\\league\\flysystem\\src\\Filesystem.php(160): League\\Flysystem\\Local\\LocalFilesystemAdapter->visibility('media/96f646eb-...')
#3 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Filesystem\\FilesystemAdapter.php(443): League\\Flysystem\\Filesystem->visibility('media/96f646eb-...')
#4 ...\\vendor\\awcodes\\filament-curator\\src\\Models\\Media.php(40): Illuminate\\Filesystem\\FilesystemAdapter->getVisibility('media/96f646eb-...')
#5 [internal function]: Awcodes\\Curator\\Models\\Media->Awcodes\\Curator\\Models\\{closure}(NULL, Array)
#6 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Database\\Eloquent\\Concerns\\HasAttributes.php(677): call_user_func(Object(Closure), NULL, Array)
#7 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Database\\Eloquent\\Concerns\\HasAttributes.php(701): Illuminate\\Database\\Eloquent\\Model->mutateAttributeMarkedAttribute('url', NULL)
#8 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Database\\Eloquent\\Concerns\\HasAttributes.php(213): Illuminate\\Database\\Eloquent\\Model->mutateAttributeForArray('url', NULL)
#9 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Database\\Eloquent\\Model.php(1638): Illuminate\\Database\\Eloquent\\Model->attributesToArray()
#10 ...\\vendor\\livewire\\livewire\\src\\HydrationMiddleware\\HydratePublicProperties.php(244): Illuminate\\Database\\Eloquent\\Model->toArray()
#11 ...\\vendor\\livewire\\livewire\\src\\HydrationMiddleware\\HydratePublicProperties.php(224): Livewire\\HydrationMiddleware\\HydratePublicProperties::filterData(Object(Awcodes\\Curator\\Resources\\MediaResource\\EditMedia), 'record')
#12 ...\\vendor\\livewire\\livewire\\src\\HydrationMiddleware\\HydratePublicProperties.php(112): Livewire\\HydrationMiddleware\\HydratePublicProperties::dehydrateModel(Object(Awcodes\\Curator\\Models\\Media), 'record', Object(Livewire\\Response), Object(Awcodes\\Curator\\Resources\\MediaResource\\EditMedia))
#13 [internal function]: Livewire\\HydrationMiddleware\\HydratePublicProperties::Livewire\\HydrationMiddleware\\{closure}(Object(Awcodes\\Curator\\Models\\Media), 'record')
#14 ...\\vendor\\livewire\\livewire\\src\\HydrationMiddleware\\HydratePublicProperties.php(145): array_walk(Array, Object(Closure))
#15 ...\\vendor\\livewire\\livewire\\src\\LifecycleManager.php(154): Livewire\\HydrationMiddleware\\HydratePublicProperties::dehydrate(Object(Awcodes\\Curator\\Resources\\MediaResource\\EditMedia), Object(Livewire\\Response))
#16 ...\\vendor\\livewire\\livewire\\src\\Connection\\ConnectionHandler.php(15): Livewire\\LifecycleManager->dehydrate()
#17 ...\\vendor\\livewire\\livewire\\src\\Controllers\\HttpConnectionHandler.php(21): Livewire\\Connection\\ConnectionHandler->handle(Array)
#18 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\ControllerDispatcher.php(46): Livewire\\Controllers\\HttpConnectionHandler->__invoke('awcodes.curator...')
#19 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Route.php(260): Illuminate\\Routing\\ControllerDispatcher->dispatch(Object(Illuminate\\Routing\\Route), Object(Livewire\\Controllers\\HttpConnectionHandler), '__invoke')
#20 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Route.php(205): Illuminate\\Routing\\Route->runController()
#21 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php(799): Illuminate\\Routing\\Route->run()
#22 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(141): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
#23 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Middleware\\SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#24 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(180): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#25 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#26 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#27 ...\\vendor\\laravel\\framework\\src\\Illuminate\\View\\Middleware\\ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#28 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(180): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#29 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Session\\Middleware\\StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#30 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Session\\Middleware\\StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest(Object(Illuminate\\Http\\Request), Object(Illuminate\\Session\\Store), Object(Closure))
#31 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(180): Illuminate\\Session\\Middleware\\StartSession->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#32 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#33 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(180): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#34 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Cookie\\Middleware\\EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#35 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(180): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#36 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#37 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php(800): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#38 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php(777): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#39 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php(741): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#40 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php(730): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#41 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#42 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(141): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#43 ...\\vendor\\livewire\\livewire\\src\\DisableBrowserCache.php(19): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#44 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(180): Livewire\\DisableBrowserCache->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#45 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#46 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#47 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\TrimStrings.php(36): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#48 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#49 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#50 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#51 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance.php(86): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#52 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#53 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Http\\Middleware\\HandleCors.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#54 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(180): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#55 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Http\\Middleware\\TrustProxies.php(39): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#56 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(180): Illuminate\\Http\\Middleware\\TrustProxies->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#57 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#58 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#59 ...\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#60 ...\\public\\index.php(52): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#61 {main}
"}

Image preview not working

preview of image not working as the screen shot below
preview error
and get the console error
preview console error
this happen when I changed the disk from public to uploads

Error after change from pdf file to image and vice versa

Screencast.from.07-07-2023.10.16.12.AM.webm

Error message: Filament\Forms\Components\BaseFileUpload::Filament\Forms\Components\{closure}(): Argument #1 ($file) must be of type Livewire\TemporaryUploadedFile|string, array given

How to reproduce:

  1. Upload new PDF file
  2. Edit file and "Upload new" tab, change it to Image
  3. After save go to "Curator" tab
  4. Create curator and save (from edit page)

The problem is gone after page refresh

Another Problem
Screencast from 07-07-2023 10:32:36 AM.webm

How to reproduce:

  1. Upload new Image file
  2. Edit file and "Upload new" tab, change it to PDF
  3. Save
  4. Try to remove from "Upload new" tab page

relationship sort order - still not saving

Relates to #206

I upgraded to v2.7.6, added order column to my relationship pivot table, tried both with and without ->orderColumn('order').

Result: after changing sort order of multiple relationship, saving the resource, and refreshing the page - sort order is not saved and order column value in database is null.

generateThumbs support for external disks

Possible solution in Media model under generateThumbs:

$image = Image::make(
    Storage::disk($this->record->disk)->url($media->filename)
);

...

Storage::disk($media->disk)->put(
				$pathinfo["dirname"] .
					"/" .
					$pathinfo["filename"] .
					"-" .
					$name .
					"." .
					$media->ext,
				$image->stream()
			);

Cant pick already uploaded video

Hello, i have field

CuratorPicker::make('video')
                ->acceptedFileTypes(['video/*'])
                ->constrained()
                ->required(),

i can add new video, but i cant pick existing video

Many to many relationship with Media

Is it possible to create a way of selecting Media for a page.

My code is trying to add the media instead of setting the media_id and using the attach. Which obivously gives error SQLSTATE[HY000]: General error: 1364 Field 'filename' doesn't have a default value

<?php

namespace App\Filament\Resources\PageResource\RelationManagers;

use Filament\Forms;
use Filament\Resources\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Resources\Table;
use Filament\Tables;
use FilamentCurator\Forms\Components\MediaPicker;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;

class MediaRelationManager extends RelationManager
{
    protected static string $relationship = 'media';

    protected static ?string $recordTitleAttribute = 'alt';

    public static function form(Form $form): Form
    {
        return $form
            ->schema([
                MediaPicker::make('filename')->label('Select')
            ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                Tables\Columns\TextColumn::make('alt'),
            ])
            ->filters([
                //
            ])
            ->headerActions([
                Tables\Actions\CreateAction::make(),
            ])
            ->actions([
                Tables\Actions\EditAction::make(),
                Tables\Actions\DeleteAction::make(),
            ])
            ->bulkActions([
                Tables\Actions\DeleteBulkAction::make(),
            ]);
    }    
}

Page class:

class Page extends Model {
    public function media()
    {
        return $this->belongsToMany(Media::class, 'page_media');
    }
}

PageResource

class PageResource extends Resource {
   ///
    public static function getRelations(): array
    {
        return [
            MediaRelationManager::class,
        ];
    }
}

CuratorPicker Not Working When Called From a Modal

Hi i have a create form page in which i have added, createOptionsForm in some Select fields , on clicking on them it opens modal to create dependent Data,
On Opening Model, i have a field to add media using Curator to the dependent data, is there any way i can open nested modals.
Currently its not working, its just give me blank page

Create Data Page

image

Create Dependent Modal Page
image

On Clicking On Add Media
image

Undefined array key "alt"

I just add

CuratorPicker::make("tour_images")
->directory('tour-images')
->preserveFilenames()
->multiple()
->relationship('tour_images', 'id'),

and when I upload and then click Insert after upload. It always give me error undefined array key "alt"

close and open popup again. And then click insert. It works properly but didnt understand why i got this error first place ?

help needed

hi how can i hide this or make it lower on mobile please help me

Capture

Change navigation label

How can I change navigation label?
According to the MediaResource class, the navigation label is always Media!

public static function getNavigationLabel(): string
{
        return Str::title(static::getPluralModelLabel()) ?? Str::title(static::getModelLabel());
}

Can I change it to below code?

public static function getNavigationLabel(): string
{
        return __('curator::views.panel.navigation_label');
}

When calling multiple CuratorPicker::make on the same for, the page crashes

Whenever I add multiple CuratorPicker::make to my form, it causes the form to throw an error.

Code:

use Awcodes\Curator\Components\Forms\CuratorPicker;

CuratorPicker::make('thumbnail')
    ->label('Thumbnail Image')
    ->disk('local')
    ->directory('selections'),
CuratorPicker::make('main_image')
    ->label('Main Image')
    ->disk('local')
    ->directory('selections'),

Error:
Awcodes\Curator\Components\Forms\CuratorPicker::Awcodes\Curator\Components\Forms\{closure}(): Argument #2 ($state) must be of type array|int|null, string given, called in...

CuratorColumn: invalid glide url

Hello, i use CuratorColumn like this

CuratorColumn::make('preview_image_id')->label('Preview Image')->size(50) or width(50)
and this is url what we have
/curator/articles/article-1.jpg?w=50px&h=40px&fit=crop&fm=webp&s=818c581169f60065dd45164ff0048c58
I think it happens because ImageColumn adds px to width and height
but w=50px is invalid parameter for glide

RTL mode

The padding or margin display is problematic when using RTL mode.

image

Repeater Bug

mountedFormAction doesn't have the right component statepath when inside a Repeater field.

Unable to update/delete media

I have set up Curator as instructed.

When I launch the modal, I can upload and preview files just fine. They are successfully persisted into the database as well, which is to say they show up in the library when I reload the page and relaunch the curator.

Problem occurs when I try to update/delete media. I get the error Call to a member function update() on array. You can substitute update() with delete() as well.

Problem is here:

public function updateFile(): void
{
    $this->selected->update($this->editMediaForm->getState());
}

For some reason, the selected property is not an array that contains instances of Media, but an array that contains arrayified Media. The setSelection() method correctly fills the selected array with Eloquent models, but by the time I get to the update method, it's not an array of Models anymore, so trying to call methods on them raises an error.

I can make it work by modifying updateFile() or destroyFile() so that they fetch the model from the DB beforehand. Something along the lines of:

public function updateFile(): void
{
    $item = Curator::getMediaModel()::whereId($media[0]['id'])->first();
    $item->update($this->editMediaForm->getState());
}

But I get the feeling that I shouldn't need to do that. Am I missing something?

Image Editing

Hello, how can I edit an image (upload a new file instead of an existing one) with the saved file name, metadata, public identifier?

SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'image'

Hello, i use the curator on 2 websites, first is running well, on the second when i use the Media field, i have this error when i try to save the record, when i enter the id of the image manually in the database it works again..

how can i solve this?

SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'image' at row 1 (SQL: update blog_postssetimage= {"id":140,"public_id":"media\/d23a7716-c7cc-44a0-b3d8-beb631ee91ac","filename":"media\/d23a7716-c7cc-44a0-b3d8-beb631ee91ac.jpg","ext":"jpg","type":"image\/jpeg","alt":null,"title":null,"description":null,"caption":null,"width":1920,"height":1030,"disk":"public","directory":"media","size":150547,"created_at":"2022-12-23T18:17:46.000000Z","updated_at":"2022-12-23T18:17:46.000000Z","url":"https:\/\/vietnamholiday.org\/storage\/media\/d23a7716-c7cc-44a0-b3d8-beb631ee91ac.jpg","thumbnail_url":"https:\/\/vietnamholiday.org\/storage\/media\/d23a7716-c7cc-44a0-b3d8-beb631ee91ac-thumbnail.jpg","medium_url":"https:\/\/vietnamholiday.org\/storage\/media\/d23a7716-c7cc-44a0-b3d8-beb631ee91ac-medium.jpg","large_url":"https:\/\/vietnamholiday.org\/storage\/media\/d23a7716-c7cc-44a0-b3d8-beb631ee91ac-large.jpg","size_for_humans":"147 KiB","hasSizes":true},blog_posts.updated_at= 2023-01-07 09:08:20 whereid= 19) {"userId":1,"exception":"[object] (Illuminate\\Database\\QueryException(code: 22001): SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'image' at row 1 (SQL: updateblog_postssetimage= {\"id\":140,\"public_id\":\"media\\/d23a7716-c7cc-44a0-b3d8-beb631ee91ac\",\"filename\":\"media\\/d23a7716-c7cc-44a0-b3d8-beb631ee91ac.jpg\",\"ext\":\"jpg\",\"type\":\"image\\/jpeg\",\"alt\":null,\"title\":null,\"description\":null,\"caption\":null,\"width\":1920,\"height\":1030,\"disk\":\"public\",\"directory\":\"media\",\"size\":150547,\"created_at\":\"2022-12-23T18:17:46.000000Z\",\"updated_at\":\"2022-12-23T18:17:46.000000Z\",\"url\":\"https:\\/\\/vietnamholiday.org\\/storage\\/media\\/d23a7716-c7cc-44a0-b3d8-beb631ee91ac.jpg\",\"thumbnail_url\":\"https:\\/\\/vietnamholiday.org\\/storage\\/media\\/d23a7716-c7cc-44a0-b3d8-beb631ee91ac-thumbnail.jpg\",\"medium_url\":\"https:\\/\\/vietnamholiday.org\\/storage\\/media\\/d23a7716-c7cc-44a0-b3d8-beb631ee91ac-medium.jpg\",\"large_url\":\"https:\\/\\/vietnamholiday.org\\/storage\\/media\\/d23a7716-c7cc-44a0-b3d8-beb631ee91ac-large.jpg\",\"size_for_humans\":\"147 KiB\",\"hasSizes\":true},blog_posts.updated_at= 2023-01-07 09:08:20 whereid = 19) at /home/ploi/vietnamholiday.org/vendor/laravel/framework/src/Illuminate/Database/Connection.php:760)

Rotation bug on curation

I'm encountering a rotation bug when creating curation. There may be multiple ways to reproduce this, but what causes it for me is:

  1. Take a picture with an iPhone in portrait orientation.
  2. Upload the picture to Curator. (Picture is oriented correctly).
  3. Create a curation (Picture is oriented correctly in the preview, but note that Cropper set 90 in the rotation)
  4. Save the curation. The curation is upside down.

From what I can tell Cropper is getting the 90 degree rotation from the EXIF data and sets this in the curation form. However when calling saveCuration the original image is loaded without the orientation correction and then it's rotated AGAIN since a 90 rotation was set by cropper.

In saveCuration you can call $image->orientate() before ->rotate() which orients the image per the EXIF date, HOWEVER it still applies Cropper's 90 degree rotation so the image will be on it's side.

As of now a workaround is to export the image using an app like Preview before uploading. Exporting the image will "bake in" the orientation preventing the bug.

Emailing the image to yourself is NOT a workaround. The iPhone will convert the HEIC into a JPG, but the EXIF data will still be attached.

Missing license file

Hey! This package looks amazing, and it would be neat to be able to use components from it. However, since there is no license file attached, no-one is allowed to use the code herein. https://choosealicense.com/no-permission/

I would suggest the MIT License, as that's what both Laravel and FilamentPHP uses.

All the best, Caen

Better support for disk IconColumn

Currently, the cloud icon only appears if the disk is set to s3 or cloudinary.
return in_array($state, ["cloudinary", "s3"]);

Allow other disks to be included in the cloud list.

Curator image not save base on format

Screenshot from 2023-07-05 16-19-01
Screenshot from 2023-07-05 16-17-34

When changing format inside curator, the image extension doesn't change base on format select, it only change the mimes type.

How to reproduce:
Edit any image, go to curator and change format

Expected result:
Image store in database with correct format selected as well in storage file

This issue is submitted base on Discord discussion

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.