kemal ozdogan
kemal ozdogan

Reputation: 81

How can i use an $photo->temporaryUrl() with an drag and drop function?

I'm currently implementing a drag-and-drop functionality for images in my project and aiming to utilize $photo->temporaryUrl() to display the image on-screen before committing to saving it. The goal is to defer saving the $photo until the user clicks the save button.

However, I'm facing a challenge in achieving this due to the nature of my drag-and-drop implementation. Unlike a conventional field, I'm uncertain about how to integrate the necessary part into the existing drag-and-drop setup.

I've explored the documentation at https://laravel-livewire.com/docs/2.x/file-uploads and attempted to replicate the Temporary Preview URLs section. Nevertheless, I'm unsure about the placement of the within the drag-and-drop structure.

I'm seeking assistance in resolving this issue, which arose from my earlier error discussed here: Stack Overflow Question.

Below is the relevant snippet from my add-products.blade.php file:

<div>
{{--    <img class="object-contain size-80 mx-auto rounded-lg mt-6 border-2 border-slate-400"--}}
{{--         src="{{$url}}"--}}
{{--         alt="Product Image">--}}

    @if ($photo)

        {{-- my img   --}}
        <img class="object-contain size-80 mx-auto rounded-lg mt-6 border-2 border-slate-400"
             src="{{$photo->temporaryUrl()}}"
             alt="Product Image">

        {{-- hidden imput so i can use this in the controllers   --}}
        <input value="{{$photo->temporaryUrl()}}" name="productImg" hidden>
    @endif

    {{var_dump($photo)}} {{-- this is for debugging --}}

    {{-- My drop and drag    --}}

    <div
        class="flex mt-5 justify-center items-center"
        x-data="drop_file_component()">

            {{-- i originally put the <input type="file" wire:model="photo"> here, but dont know if it was correct because it aint working       --}}

        <div
            class="py-6 w-96 rounded border-dashed border-2 flex flex-col justify-center items-center"
            x-bind:class="dropingFile ? 'bg-gray-400 border-gray-500' : 'border-gray-500 bg-gray-200'"
            x-on:drop="dropingFile = false"
            x-on:drop.prevent="
                handleFileDrop($event)
            "
            x-on:dragover.prevent="dropingFile = true"
            x-on:dragleave.prevent="dropingFile = false">
            <svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z" />
            </svg>
            <div class="text-center" wire:loading.remove wire.target="files">Drop Your Files Here</div>
            <div class="mt-1" wire:loading.flex wire.target="files">
                <svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-gray-700" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
                    <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
                    <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                </svg>
                <div>Processing Files</div>
            </div>
        </div>
    </div>

    <script>
    {{-- the function for the drop and drag       --}}
        function drop_file_component() {
            return {
                dropingFile: false,
                handleFileDrop(e) {
                    if (event.dataTransfer.files.length > 0) {
                        const files = e.dataTransfer.files;
                    @this.uploadMultiple('files', files,
                        (uploadedFilename) => {}, () => {}, (event) => {}
                    )
                    }
                }
            };
        }
    </script>
</div>

And here's the corresponding section from my AddProducts.php file:

namespace App\Livewire;

use AllowDynamicProperties;
use App\Models\Product;
use Illuminate\Support\Facades\Storage;
use Livewire\Component;
use Livewire\WithFileUploads;

#[AllowDynamicProperties] class AddProducts extends Component
{
    use WithFileUploads;
    protected $listeners = ['deleteDroppedImage' => 'deleteDroppedImage'];
    public $files = [];
    public $url; // Declare a public property
    public Product $product;
    public $photo;

    // How the example code looked like 
    public function updatedPhoto() {
        $this->validate([
            'photo' => 'image|max:102400', // 100MB Max
        ]);
    }
    
    // These 2 functions are what i had before the updatedPhoto function
    public function render()
    {
        $this->url = $this->url ?? 'https://img.freepik.com/premium-vector/default-image-icon-vector-missing-picture-page-website-design-mobile-app-no-photo-available_87543-11093.jpg';
        return view('livewire.add-products');
    }

    public function updatedFiles()
    {

        $this->validate([
            'files.*' => 'image|max:102400', // 100MB Max
        ]);
//        dd($this->files);
//        // You can do whatever you want to do with $this->files here
//        foreach ($this->files as $file) {
//            $path = $file->storePublicly('photos', 'public'); // Store the file publicly in the 'public' disk and get the path
//            $path = "storage/" . $path;
//            $newUrl = asset($path); // Get the URL of the new file
//
//
//            $this->url = $newUrl; // Update the URL of the file
//        }
    }
}

Currently, the $photo variable is returning NULL even after dropping an image into the drag-and-drop area. Resolving this issue would pave the way for further improvements in saving the image correctly.

Any insights or guidance on resolving this matter would be greatly appreciated. Thank you for your assistance in advance!

Edit: After dropping an image, the files = [] array contains the following data:

array:1 [▼
  0 => Livewire\Features\SupportFileUploads\TemporaryUploadedFile {#1697 ▼
    -test: false
    -originalName: "dfbvRDOpvwwUbx3IKkHSVNH2dd9u21-metaZmVlXzc4Nl81ODdfcG5nLndlYnA=-.webp"
    -mimeType: "application/octet-stream"
    -error: 0
    #hashName: null
    #disk: "local"
    #storage: Illuminate\Filesystem\FilesystemAdapter {#1711 ▶}
    #path: "livewire-tmp/dfbvRDOpvwwUbx3IKkHSVNH2dd9u21-metaZmVlXzc4Nl81ODdfcG5nLndlYnA=-.webp"
    path: "/var/www/html/storage/app/livewire-tmp"
    filename: "dfbvRDOpvwwUbx3IKkHSVNH2dd9u21-metaZmVlXzc4Nl81ODdfcG5nLndlYnA=-.webp"
    basename: "phposZkW0"
    pathname: "/var/www/html/storage/app/livewire-tmp/dfbvRDOpvwwUbx3IKkHSVNH2dd9u21-metaZmVlXzc4Nl81ODdfcG5nLndlYnA=-.webp"
    extension: ""
    realPath: "/var/www/html/storage/app/livewire-tmp/dfbvRDOpvwwUbx3IKkHSVNH2dd9u21-metaZmVlXzc4Nl81ODdfcG5nLndlYnA=-.webp"
    size: 75844
    writable: false
    readable: false
    executable: false
    file: false
    dir: false
    link: false
  }
]

Upvotes: 0

Views: 128

Answers (1)

Adam Phillips
Adam Phillips

Reputation: 233

Here is an implementation that I am using currently to do something similar using Livewire, and Alpine.

After getting the file and parsing it with Livewire, I use Alpine to bind the style tag to set the background image of a container.

x-bind:style="'background-image: url(\'' + photoPreview + '\');'"

create.blade.php

<?php

use App\Models\Company;
use Livewire\WithFileUploads;
use Livewire\Volt\Component;
use Livewire\Attributes\Validate;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

new class extends Component {
    use WithFileUploads;


    ...
    #[Validate('required|image|max:5120')]
    public $photo;
    #[Validate('nullable|string')]
    public $logo_path;

    public function mount(): void
    {
        ...
    }

    public function store()
    {
        $validated = $this->validate();

        if ($company = auth()->user()->company()->create($validated)) {

            if (!empty($this->photo)) {
                $path = 'public/brand-logos';
                $path = 'storage/' . $this->photo->storePublicly(path: $path);
                $path = str_replace('public/', '', $path);
                $company->forceFill([
                    'logo_path' => $path
                ])->save();
            }

            if (empty($this->parent_id)) {
                $company->saveAsRoot();
            } else {
                $parent = Company::find($this->parent_id);

                $parent->appendNode($company);
            }

            $this->photo = null;
            $this->logo_path = null;

            request()->session()->flash('toast', 'Brand successfully created!');
            request()->session()->flash('toast_type', 'success');

            return redirect()->route('employee.companies.edit', [$company]);
        }
    }
}; ?>

<div>
    <form wire:submit="store">
        <div class="card custom-card">
            <div class="card-body">
                <div
                    class="form-grid"
                    x-data="{
                            upload: false,
                            photoName: null,
                            photoPreview: null,
                            uploading: false,
                            progress: 0
                        }"
                    x-on:livewire-upload-start="uploading = true"
                    x-on:livewire-upload-finish="
                            uploading = false
                        "
                    x-on:livewire-upload-cancel="uploading = false"
                    x-on:livewire-upload-error="uploading = false"
                    x-on:livewire-upload-progress="progress = $event.detail.progress"
                >
                    <!-- Dropzone and input -->
                    <div class="col-span-12 lg:col-span-6">
                        <div
                            class="pb-2 flex items-center text-xs text-gray-400 uppercase before:flex-[1_1_0%] before:border-t before:border-gray-200 before:me-6 after:flex-[1_1_0%] after:border-t after:border-gray-200 after:ms-6 dark:text-gray-500 dark:before:border-gray-600 dark:after:border-gray-600">
                            Logo upload
                        </div>
                        <div id="droparea" class="flex items-center justify-center w-full">
                            <label for="photo"
                                   class="flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-bray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600">
                                <div class="flex flex-col items-center justify-center pt-5 pb-6">
                                    <svg class="w-8 h-8 mb-4 text-gray-500 dark:text-gray-400" aria-hidden="true"
                                         xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16">
                                        <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
                                              stroke-width="2"
                                              d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"/>
                                    </svg>
                                    <p class="mb-2 text-sm text-gray-500 dark:text-gray-400"><span
                                            class="font-semibold">Click to upload</span> or drag and drop</p>
                                    <p class="text-xs text-gray-500 dark:text-gray-400">SVG, PNG, JPG or GIF</p>
                                </div>
                                <input
                                    type="file"
                                    id="photo"
                                    class="hidden"
                                    accept="image/*"
                                    wire:model.live="photo"
                                    x-ref="photo"
                                    x-on:change="
                                            photoName = $refs.photo.files[0].name;
                                            const reader = new FileReader();
                                            reader.onload = (e) => {
                                                photoPreview = e.target.result;
                                            };
                                            reader.readAsDataURL($refs.photo.files[0]);

                                            upload = true;
                                        "
                                />
                            </label>
                        </div>
                    </div>

                    <!-- Previewer -->
                    <div class="col-span-12 lg:col-span-6">
                        <div
                            class="pb-2 flex items-center text-xs text-gray-400 uppercase before:flex-[1_1_0%] before:border-t before:border-gray-200 before:me-6 after:flex-[1_1_0%] after:border-t after:border-gray-200 after:ms-6 dark:text-gray-500 dark:before:border-gray-600 dark:after:border-gray-600">
                            Logo preview
                        </div>
                        <div class="flex items-center justify-center w-full">
                            <div
                                class="flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-bray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600">
                                <div x-show="uploading" class="flex items-center gap-x-3 whitespace-nowrap">
                                    <div class="progress" role="progressbar" aria-label="Upload progress" aria-valuenow="0"
                                         aria-valuemin="0" aria-valuemax="100">
                                        <progress class="flex flex-col justify-center rounded-full overflow-hidden bg-blue-600 text-xs text-white text-center whitespace-nowrap transition duration-500 dark:bg-blue-500" max="100" x-bind:value="progress"></progress>
                                    </div>
                                </div>
                                <span
                                    x-bind:class="(progress === 100 && !uploading) ? 'inline-block' : 'hidden'"
                                    class="w-full h-full bg-contain bg-no-repeat bg-center"
                                    x-bind:style="'background-image: url(\'' + photoPreview + '\');'"
                                    id="logo-preview"
                                >
                                    </span>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="card-footer">
                <x-submit id="company-create" />
            </div>
        </div>
    </form>
    @script
    <script>
        const e = document.querySelector("#droparea"), a = document.querySelector("#photo");

        function u(e) {
            e.preventDefault(), e.stopPropagation()
        }

        e.addEventListener("drop", (e => {
            a.files = e.dataTransfer.files, a.dispatchEvent(new Event("change")), e.preventDefault()
        })), ["dragenter", "dragover", "dragleave"].forEach((t => {
            e.addEventListener(t, u, !1)
        }));

        const iwoPrefix = document.querySelector("#iwo-prefix"),
            iwoMaxLength = document.querySelector("#iwo-max-length"),
            iwoPostfixIncrement = document.querySelector("#iwo-postfix-increment"),
            iwoExampleInput = document.querySelector("#example-work-order-input"),
            iwoExampleOutput = document.querySelector("#example-work-order-output");
        let iwoText = "123456789";
        iwoExampleInput.innerText = iwoText, iwoExampleOutput.innerText = iwoPrefix.value + iwoText.substring(iwoText.length - ("" !== iwoMaxLength.value && 0 !== iwoMaxLength.value ? iwoMaxLength.value : 6), iwoText.length) + "-" + ("" !== iwoPostfixIncrement.value && 0 !== iwoPostfixIncrement.value ? iwoPostfixIncrement.value : 10), [iwoPrefix, iwoMaxLength, iwoPostfixIncrement].forEach((e => {
            e.addEventListener("change", (() => {
                iwoExampleOutput.innerText = iwoPrefix.value + iwoText.substring(iwoText.length - ("" !== iwoMaxLength.value && 0 !== iwoMaxLength.value ? iwoMaxLength.value : 6), iwoText.length) + "-" + ("" !== iwoPostfixIncrement.value && 0 !== iwoPostfixIncrement.value ? iwoPostfixIncrement.value : 10)
            }))
        }));

    </script>
    @endscript
</div>

Upvotes: 0

Related Questions