GithubHelp home page GithubHelp logo

qcod / laravel-imageup Goto Github PK

View Code? Open in Web Editor NEW
728.0 21.0 60.0 113 KB

Auto Image & file upload, resize and crop for Laravel eloquent model using Intervention image

License: MIT License

PHP 100.00%
laravel image upload image-resize image-crop php eloquent trait

laravel-imageup's People

Contributors

dmitriymikheev avatar joaorobertopb avatar laravel-shift avatar lfelizari avatar pacoorozco avatar samnela avatar saqueib avatar shadowalker89 avatar tomredhot avatar vool 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

laravel-imageup's Issues

Replace old image

Great package ! Curently if I upload an image for my client logo, it saves to S3 like a charm. But, if my client update his logo, it works but it doesn't delete the old one in the folder. Any improvements for this ?

Ajax Image Upload

Hi,

Can we add ajax upload and preview thing using this package?

Thanks

On installation. composer require qcod/imageup facing error.


  [InvalidArgumentException]
  Could not find a matching version of package qcod/imageup. Check the package spelling, your version constraint and
  that the package is available in a stability which matches your minimum-stability (dev).


require [--dev] [--prefer-source] [--prefer-dist] [--no-progress] [--no-suggest] [--no-update] [--no-scripts] [--update-no-dev] [--update-with-dependencies] [--update-with-all-dependencies] [--ignore-platform-reqs] [--prefer-stable] [--prefer-lowest] [--sort-packages] [-o|--optimize-autoloader] [-a|--classmap-authoritative] [--apcu-autoloader] [--] [<packages>]...

Here is my env file.

{
"name": "laravel/laravel",
"description": "The Laravel Framework.",
"keywords": ["framework", "laravel"],
"license": "MIT",
"type": "project",
"require": {
"php": "^7.1.3",
"fideloper/proxy": "^4.0",
"laracasts/flash": "^3.0",
"laravel/framework": "5.7.*",
"laravel/tinker": "^1.0",
"laravelcollective/html": "^5.2.0",
"spatie/laravel-permission": "^2.20",
"spatie/laravel-view-models": "^1.0"
},
"require-dev": {
"beyondcode/laravel-dump-server": "^1.0",
"filp/whoops": "^2.0",
"fzaninotto/faker": "^1.4",
"mockery/mockery": "^1.0",
"nunomaduro/collision": "^2.0",
"phpunit/phpunit": "^7.0"
},
"autoload": {
"classmap": [
"database/seeds",
"database/factories"
],
"psr-4": {
"App\": "app/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\": "tests/"
}
},
"extra": {
"laravel": {
"dont-discover": [
]
}
},
"scripts": {
"post-root-package-install": [
"@php -r "file_exists('.env') || copy('.env.example', '.env');""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi"
],
"post-autoload-dump": [
"Illuminate\Foundation\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
]
},
"config": {
"preferred-install": "dist",
"sort-packages": true,
"optimize-autoloader": true
},
"minimum-stability": "dev",
"prefer-stable": true
}

Feature Request: Easy way to make the package work with intervention cache

Trying out this package and quite like so far!

However we almost always uses Intervention cache package with the Intervention image package to have easy access to multiple image sizes via the url caching / templating.

Would be wonderful if this package had an easy way to make use of the caching package.

I guess it would be able to be installed manually and just using the database column data to seup the custom URLs for the images in need. But would be nice if methods like $model->imageTag() had the support.

Thanks.

[Laravel 5.8] Image not uploading

Hi, I have issue with this package. I'm on Laravel v5.8.34 and Imageup v1.0.6

I have followed all instructions, checked #28 and #12 and still cannot make it work.

Config

<?php

return [

    /**
     * Default upload storage disk
     */
    'upload_disk' => 'public',

    /**
     * Default Image upload directory on the disc
     * eg. 'uploads' or 'user/avatar'
     */
    'upload_directory' => 'uploads',

    /**
     * Auto upload images from incoming Request if same named field or
     * file_input field on option present upon model update and create.
     * can be override in individual field options
     */
    'auto_upload_images' => true,

    /**
     * It will auto delete images once record is deleted from database
     */
    'auto_delete_images' => true,

    /**
     * Set an image quality
     */
    'resize_image_quality' => 80
];

Model

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

use QCod\ImageUp\HasImageUploads;

use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;

use Carbon\Carbon;

class Category extends Model
{

    use HasSlug;
    
    /**
     * Get the options for generating the slug.
     */
    public function getSlugOptions() : SlugOptions
    {
        return SlugOptions::create()
            ->generateSlugsFrom('title')
            ->saveSlugsTo('slug');
    }

    /**
     * Get the route key for the model.
     *
     * @return string
     */
    public function getRouteKeyName()
    {
        return 'slug';
    }

    protected $fillable = [
        'title',
        'photo',
    ];

    use HasImageUploads;


    
    // all the images fields for model
    protected static $imageFields = [
        'photo' => [
            // width to resize image after upload
            'width' => 150,
            
            // height to resize image after upload
            'height' => 150,
            
            // set true to crop image with the given width/height and you can also pass arr [x,y] coordinate for crop.
            'crop' => true,
            
            // what disk you want to upload, default config('imageup.upload_disk')
            'disk' => 'public',
            
            // a folder path on the above disk, default config('imageup.upload_directory')
            'path' => 'img/categories',
            
            // placeholder image if image field is empty
            'placeholder' => '/public/img/placeholder_150.png',
            
            // validation rules when uploading image
            'rules' => 'image|max:1000',
            
            // if request file is don't have same name, default will be the field name
            'file_input' => 'photo',
            
            // a hook that is triggered before the image is saved
            'before_save' => BlurFilter::class,
            
            // a hook that is triggered after the image is saved
            'after_save' => CreateWatermarkImage::class
        ],
    ];

    protected function photoUploadFilePath($file) {
        $today = Carbon::now();
        $date = $today->toDateString();
        return $date . '-' . Str::random(5) . '.' . $file->getClientOriginalExtension();
    }
}

Controller

/**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function save(Request $request)
    {
        $request->validate(
            [
                'category_title' => 'required|max:60'
            ]
        );

        $categoryTitle = $request->category_title;
        $categoryPhoto = $request->file('category_photo');
        
        try {
            $category = new Category;
            $category->title = $categoryTitle;
            $category->uploadImage($categoryPhoto, 'photo');
            $category->save();
            return back();
        } catch(Exception $e) {
            dd($e);
        }
    }

Please advise.

Laravel 8 support

Just a few hours before the release, can you please add the L8 support ?

Laravel 5.8 Support

Laravel 5.8 was released two days ago, while trying to upgrade my laravel version from 5.7 to 5.8 it doesnt allow me to, since this package is conflicting with the upgrade.

Laravel V6 Call to undefined function QCod\ImageUp\camel_case()

When calling the uploadImage method
An exception occurs

Call to undefined function QCod \ ImageUp \ camel_case ()

File: vendor / qcod / laravel-imageup / src / HasImageUploads.php
Line: 458
$ pathOverrideMethod = camel_case (strtolower ($ this-> uploadFieldName). 'UploadFilePath');

Option for not update database

Above all, great work!
I have a question / request.
Is it possible to have an option for not to save a field in database?
I explain :
I want to get all the power of your system (replace/delete/helpers etc...), but without create a new field in database for each iteration of an initial image.
image
(with this "image300" is a thumbnail of "image" )
This work perfectly ONLY if field "image300" exist in database.

To be clear, i dream of an option for $imageFields like "auto_update_database"=> false .
Do you think is possible?

Then maybe I missed something
Thanks!

[BUG] - Images not Auto Uploading

Detailed description

Images are not uploaded automatically when some field in $imageFields has no options.

Context

For exemple:

class User extends Model {
    use HasImageUploads;
    
    protected static $imageFields = [
        'cover', 'avatar'
    ];
}

There are no tests covering the automatic upload of images without options...

In method AutoUpload():

protected function autoUpload()
{
foreach ($this->getDefinedImageFields() as $field => $options) {
// check if global upload is allowed, then in override in option
$autoUploadAllowed = array_get($options, 'auto_upload', $this->canAutoUploadImages());
if (is_array($options) && count($options) && $autoUploadAllowed) {
// get the input file name
$requestFileName = array_get($options, 'file_input', $field);
// if request has the file upload it
if (request()->hasFile($requestFileName)) {
$this->uploadImage(
request()->file($requestFileName),
$field
);
}
}
}
}

Following the example, in the first foreach interaction, $field is 0 and $options has the value of "cover"
Note the condition of line 473! This image will never be sent! Because $options is string and not array.

These methods also have a bug:

  • getImageFieldOptions()
  • getImageFieldName()

If some field in $imageFields has no options, some tests will fail ... For example:

function it_returns_field_name_of_first_field()
{
$user = new User();
$fieldOption = [
'avatar' => ['width' => 200],
'logo' => ['width' => 400, 'height' => 400]
];
$user->setImagesField($fieldOption);
$this->assertEquals('avatar', $user->getImageFieldName());
$this->assertEquals('logo', $user->getImageFieldName('logo'));
}

If you replace the line value 136 from "'avatar' => ['width' => 200]" to '''avatar'''. The test will fail because the getImageFieldName() method will return 0 and not 'avatar'...

Possible implementation

I'm working to fix this. 😉

My environment

  • Laradock (PHP 7.2):
  • Operating System: Ubuntu 18.04

404, page not found.

Hello, well, everything goes as planned.

Followed the docs, images are getting uploaded, but... at the time when I upload the image, it does get stored in the /uploads folder. The thing is that I've already linked the storage and something is making the app display a 404 (don't know really why).

The name of the image matches the one stored in the DB (uploads/nU04FknEVfLtk73ZoZcOK7KEktSFCuLAOxhnQHiP.jpeg)

the one saved is nU04FknEVfLtk73ZoZcOK7KEktSFCuLAOxhnQHiP.jpeg (inside the uploads folder). Any idea on this?

[feature] Extend the package to handle more than images

Hi,

I really love the way you handle image uploads via this package.
It makes working with file uploads more enjoyable.

Now the package already looks great, but how about handling any type of files auto upload and not limit it to images.

Now of course, width, height ... attributes are reserved only for images.

The beauty of it that when using this package you do not need to care about doing files uploads in the controller, everything will go smoothly under the hood.

I'll gladly make a PR for this feature if you are open for it.

ERROR "detail": "League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/syofyan/dreamworld/project/pmo-9/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 159",

"detail": "League\\Flysystem\\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/syofyan/dreamworld/project/pmo-9/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 159",

Its error on this line:

    private function deleteUploadedFile($filePath)
    {
        if ($this->getStorageDisk()->exists($filePath)) {
            $this->getStorageDisk()->delete($filePath);
        }
    }

to this line:

        // delete old file
        if ($currentFile != $filePath) {
            $this->deleteImage($currentFile);
        }

Feature Request: A way to setup a custom filenames

Not entire sure that its still is as important but for SEO it sometimes makes sense that image files is named in a special way.

Fx. if its a webshop and the product image is named with the slug of the product. This allows people searching for images to have greater change to find what they are looking for.

error Driver [] is not supported.

Hi,

i am getting Driver [] is not supported. error while saving model

imageup.php

return [

/**
 * Default upload storage disk
 */
    'upload_disk' => 'public',

    /**
     * Default Image upload directory on the disc
     * eg. 'uploads' or 'user/avatar'
     */
    'upload_directory' => 'uploads',

    /**
     * Auto upload images from incoming Request if same named field or
     * file_input field on option present upon model update and create.
     * can be override in individual field options
     */
    'auto_upload_images' => true,

    /**
     * It will auto delete images once record is deleted from database
     */
    'auto_delete_images' => true,

    /**
     * Set an image quality
     */
    'resize_image_quality' => 80
];

Model

class User extends Authenticatable implements AuditableContract
{
    use HasApiTokens, Notifiable, SoftDeletes, Auditable, HasImageUploads;

    // mark all the columns as image fields
    protected static $imageFields = [
        'avatar' => [
            // width to resize image after upload
            'width' => 200,
            
            // height to resize image after upload
            'height' => 200,
            
            // set true to crop image with the given width/height and you can also pass arr [x,y] coordinate for crop.
            'crop' => true,
            
            // what disk you want to upload, default config('imageup.upload_disk')
            'disk' => 'public',
            
            // a folder path on the above disk, default config('imageup.upload_directory')
            'path' => 'avatars',
            
            // placeholder image if image field is empty
            'placeholder' => '/images/avatar-placeholder.svg',
            
            // validation rules when uploading image
            'rules' => 'image|max:2000',
            
            // override global auto upload setting coming from config('imageup.auto_upload_images')
            'auto_upload' => false,
            
            // if request file is don't have same name, default will be the field name
            'file_input' => 'avatar',
            
            // // a hook that is triggered before the image is saved
            // 'before_save' => BlurFilter::class,
            
            // // a hook that is triggered after the image is saved
            // 'after_save' => CreateWatermarkImage::class
        ]
    ];
    protected $softDelete = true;`

Incorrect docblocks and no typehints and return types

Hello,

I was trying to extract an interface of the HasImageUploads trait, but it seems it has a lot of incorrect docblocks. For instance:

@param null $field

This is used on imageUrl(), fileUrl(), imageTag() and uploadImage(). The above states that the parameter should always be null, when you are trying to say it must be either a string or null.

Also, a lot of the docblocks are missing any sort of type declarations, for instance:

/**
 * Upload and resize image
 *
 * @param $imageFile
 * @param null $field
 * @return $this
 * @throws InvalidUploadFieldException|\Exception
 */
public function uploadImage($imageFile, $field = null)

It would be really useful here to know what $imageFile is supposed to be.

Also, you are making this package available for php 7.2 and onwards, which means that you can use typehints and return types in your method signatures.

Image not uploading

Image not uploading when imageFields path has folder inside folder but generate and save image name with path in database

Image not replacing

I don't know if it is bug or not. I have been trying to update an image but the old image is not deleted/replaced.

My old code which doesn't delete old image

if(isset($request->photo)) {
$model->relation->uploadImage($request->photo);
}

I change it to below and it does replace old image with the new one

// Assign it to variable
$myPhoto = $request->photo;

if(isset($myPhoto)) {
$model->relation->uploadImage($myPhoto);
}

I'm on Laravel 5.8
Imageup v1.0.6

Can't resize image on upload

Hi!

Trying to upload the image and resize it on upload.

Model (Profile):

`use HasImageUploads;

protected static $imageFields = [
    'avatar' => [
        [
            'width' => 300,
            'height' => 300,
            'crop' => false,
            'placeholder' => 'http://placehold.it/300x300',
            'rules' => 'image|max:2000',

        ]
    ]
];`

Controller:
`public function uploadAvatar(Request $request, User $user){

    $request->validate([
        'image' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
    ]);

    $image = $request->file('image');

    $user->profile->uploadImage($image, 'avatar');

    return response()->json(['url'=>$user->profile->avatar], 200);
}`

But as a result I'm getting full-size image. What am I doing wrong? Any suggestions appreciated :)

Thanks in advance :)

Cannot access uploaded image

First off, thanks for the amazing package.
I have setup the package however, i am unable to get the image to show as i see only a broken image icon. On my network tab i see that the status code is 404 meaning not found. Please any help.

I changed the APP URL to to see if it was the problem but still the issue remains the same.

When a rule is triggered, the record is still saved to the database.

Hello! Thank you for your hard work.
I have a problem.

  1. I write the rule
protected static $ imageFields = [
         'photo' => [
             'height' => 600,
             'width' => 600,
             'path' => 'photos',
             'rules' => 'image|max: 2000',
             'auto_upload' => true

         ]
     ];
  1. I save data in the controller
    $person->update ($request->all());

  2. When you try to upload a file larger than 2MB, an error is displayed.
    But in the photo field, the value / tmp / phpNqTpMr is saved

It seems so should not be)
Is this an error or rules need to be customized in a special way?

Image dont't upload

Hello,
I try laravel-imageup in local with phpstorm and .laragon
Everything ok but images never upload anywhere.
Actually i'm with basic configuration so with imageup.php =

/**
 * Default upload storage disk
 */
'upload_disk' => 'public',


/**
 * Default Image upload directory on the disc
 * eg. 'uploads' or 'user/avatar'
 */
'upload_directory' => 'avatars',

/**
 * Auto upload images from incoming Request if same named field or
 * file_input field on option present upon model update and create.
 * can be override in individual field options
 */
'auto_upload_images' => true,

/**
 * It will auto delete images once record is deleted from database
 */
'auto_delete_images' => true,

/**
 * Set an image quality
 */
'resize_image_quality' => 80

];
`
I use avatars directory for avatars in profil so i'm sur permission on it is ok.
URL of image is saved correctly in DB and i have no error message anywhere (log, debugbar...).
DO you have idea of why i can't upload ?

Feature Request: Option to upload non-image documents too

Thanks for nice library!

I saw right now it only works for image files:

protected static $imageFields = [
        'cover', 'avatar'
];

Would be great if support of other file types (pdf, doc, xls, csv, etc) is also added something like:

protected static $fileFields = [
        'pdf_file', 'my_pdf_file'
];

Thanks

Please add Laravel 7 support!

Thanks for the simple and convenient package, please add support for Laravel 7. When installing, I get this:

Problem 1 - Conclusion: remove laravel/framework v7.2.1 - Conclusion: don't install laravel/framework v7.2.1

Thanks!

[BUG] - Mutator method

Detailed description

The bug happens if the model has a mutator method to format the value of the attribute.

Context

For example, the user can show the URL of the image directly from the model:

class User extends Model {
    use HasImageUploads;
    
    protected static $imageFields = [
        'avatar'
    ];

    public function getAvatarAttribute($value)
    {
        return $this->imageUrl('avatar');
    }

}

This package uses the Eloquent getAttributeValue() method to get the value of the attribute, but...

See:

public function getAttributeValue($key)
    {
        $value = $this->getAttributeFromArray($key);
        // If the attribute has a get mutator, we will call that then return what
        // it returns as the value, which is useful for transforming values on
        // retrieval from the model to a form that is more useful for usage.
        if ($this->hasGetMutator($key)) {
            return $this->mutateAttribute($key, $value);
        }

Source code: https://github.com/laravel/framework/blob/734d7584e4f1cdf306fb611fa6266113f962dc56/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php#L336-L345

💥 ‼️

Possible implementation

  • Replace method from getAttributeValue() to getOriginal()
  • Implement new tests for this behavior
  • Change all tests to use the getOriginal() method

I'm finishing a PR to fix this 😄 ...

My environment

  • Laradock (PHP 7.2):
  • Operating System: Ubuntu 18.04

Proposal for revision

Hi, Mohd Saqueib Ansari!

Thank you very much for developing this package.

I have a suggestion for revision.
In the example, you describe a use case
 $ user->update ($request->all());

But I often have a need to pass $ request->book parameters to the update method where the information about the file to be downloaded is kept, for example $request->book->cover
 $book->update ($request->book);

But the autoUpload method cannot automatically download this file, because it checks the existence of the file so

 if (request () -> hasFile ($ requestFileName)) {
                $ this-> uploadImage (
                    request () -> file ($ requestFileName),
                    $ field
                );
            }

I have a suggestion to replace this part of the code, so

if (! empty ($ this-> attributes [$ requestFileName]) and $ this-> attributes [$ requestFileName] instanceof UploadedFile) {
                $ this-> uploadImage (
                    $ this-> attributes [$ requestFileName],
                    $ field
                );
            }

As a result, the function will look like this

protected function autoUpload()
    {
        foreach ($this->getDefinedUploadFields() as $key => $val) {
            $field = is_int($key) ? $val : $key;
            $options = Arr::wrap($val);

            // check if global upload is allowed, then in override in option
            $autoUploadAllowed = Arr::get($options, 'auto_upload', $this->canAutoUploadImages());

            if (!$autoUploadAllowed) {
                continue;
            }

            // get the input file name
            $requestFileName = Arr::get($options, 'file_input', $field);

            if (!empty($this->attributes[$requestFileName]) and $this->attributes[$requestFileName] instanceof UploadedFile) {
                $this->uploadImage(
                    $this->attributes[$requestFileName],
                    $field
                );
            }
        }
    }

Do you understand my point?
Can you include this change in the release?

Placeholder file location

I don't know if I am being stupid here, but placeholder images are not working for me, just wondering where the default file location is ?

updating the model stores the tmp file location

It works on create with no issues, but when i update the model with ### the image , the stored file in the database is the temp location not the permanent location "tmp\phpE37E.tmp"

My Model
`namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use QCod\ImageUp\HasImageUploads;

class Partner extends Model
{
use SoftDeletes, HasImageUploads;

protected $table='partners';
protected $guarded = ['id'];

protected static $imageFields = ['logo'];
// which disk to use for upload, can be override by field options
protected $imagesUploadDisk = 'local';
// path in disk to use for upload, can be override by field options
protected $imagesUploadPath = 'uploads/partners';
// auto upload allowed
protected $autoUploadImages = true;

// override cover file name
protected function logoUploadFilePath($file) {
    return $this->id . '-logo-image.jpg';
}

}
My Controller
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
try{
$inputs = $request->except(['_token','_method']);
return Partner::where('id',$id)->update($inputs);
}catch(Exception $e){
return $e->getMessage();
}
}`

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.