Sortable icon indicating copy to clipboard operation
Sortable copied to clipboard

Items often flickering/being removed when showing a preview for each item

Open qomhmd opened this issue 1 year ago • 0 comments

I try to list files selected using multiple file input withthe code below (also available at jsbin). When trying to move li items to sort and nest them, they start flickering and sometimes are dropped from the list and completely removed from DOM. Sometimes when two or three items were dropped, the list works well.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Bootstrap-Flask Demo Application</title>

    <style>
        .handle {
            cursor: -webkit-grabbing;
            cursor: move;
        }
    </style>
</head>

<body>
    <input class="form-control" id="files" multiple="" name="files" required="" type="file">
    <div id="files-selected" class="mb-3">
        <ul class="list-group very-first-parent nested-sortable h-200">

        </ul>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/dcraw"></script>
    <script src="https://sortablejs.github.io/Sortable/Sortable.js"></script>
    <script>
        var filesField;
        var filesSelected;
        document.addEventListener('DOMContentLoaded', function () {
            filesField = document.getElementById("files");
            filesSelected = document.getElementById("files-selected");

            filesField.addEventListener('change', function (e) {
                var files = e.target.files;

                var reader = [];
                var filenames = Array.from(files).map(file => file.name);

                filesSelected.querySelector('.very-first-parent').innerHTML = '';
                filenames.forEach((filename, index) => {
                    // alert('file selected');
                    var file = files[index];
                    var li = document.createElement('li');
                    li.className = 'list-group-item';
                    li.innerHTML = `
                    <div class="d-flex align-items-center">
                        <i class="handle bi-arrows-move"></i>
                        <button type="button" class="btn btn-danger me-3 ms-3" onclick="dismiss(this)">
                            <span aria-hidden="true">&times;</span>
                        </button>
                        <span class="file-name">${filename}</span>
                        <!--spinner--><div class="spinner spinner-border ms-auto" role="status"><span class="visually-hidden">Loading...</span></div><!--/spinner-->
                    </div>
                    <ul class="list-group nested-sortable"></ul>
                    `;
                    showPreview(file, li);
                    filesSelected.querySelector('.very-first-parent').appendChild(li);
                });

                makeNestedSortable();
            });
        });
        function isImage(file) {
            const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp'];
            return imageTypes.includes(file.type);
        }

        function isRawImage(file) {
            const rawImageExtensions = ['.nef', '.cr2', '.tiff'];
            return rawImageExtensions.some(ext => file.name.toLowerCase().endsWith(ext));
        }

        function isVideo(file) {
            const videoTypes = ['video/mp4', 'video/quicktime'];
            return videoTypes.includes(file.type);
        }

        function replaceSpinner(string_old, string_new) {
            return string_old.replace(/<!--spinner-->.*<!--\/spinner-->/gi, string_new)
        }

        function showPreview(file, li) {
            if (isImage(file)) {
                const reader = new FileReader();
                reader.onload = function (e) {
                    li.innerHTML = replaceSpinner(li.innerHTML, `<img class="preview ms-auto" src="${e.target.result}" height="70" style="max-height: 70px;">`);
                };
                reader.readAsDataURL(file);
            } else if (isRawImage(file)) {
                // You can use a library like raw.js to decode raw images and show a preview
                const reader = new FileReader();
                reader.onload = (function (o) {
                    return function (e) {
                        // Get the image file as a buffer
                        var buf = new Uint8Array(e.currentTarget.result);

                        // Get the RAW metadata
                        var metadata = dcraw(buf, { verbose: true, identify: true }).split('\n').filter(String);

                        // Extract the thumbnail
                        var thumbnail = dcraw(buf, { extractThumbnail: true });

                        // Create thumbnail
                        var blob = new Blob([thumbnail], { type: "image/jpeg" });
                        var urlCreator = window.URL || window.webkitURL;
                        var imageUrl = urlCreator.createObjectURL(blob);
                        li.innerHTML = replaceSpinner(li.innerHTML, `<img class="preview ms-auto" src="${imageUrl}" height="70" style="max-height: 70px;">`);
                    };
                })(file);
                reader.readAsArrayBuffer(file);
            } else if (isVideo(file)) {
                li.innerHTML = replaceSpinner(li.innerHTML, `<video src="${URL.createObjectURL(file)}" class="ms-auto" controls height="70" style="max-height: 70px;"></video>`);
            } else {
                li.innerHTML = replaceSpinner(li.innerHTML, `<span class="preview ms-auto">Preview not available</span>`);
            }
        }

        function dismiss(target) {
            var li = target.closest('li');
            var fileToDelete = li.querySelector('span.file-name').textContent;

            var dt = new DataTransfer();
            Array.from(filesField.files).forEach((file, i) => {
                if (file.name !== fileToDelete)
                    dt.items.add(file)

                filesField.files = dt.files // this will trigger a change event
            });

            li.remove();
        }

        function makeNestedSortable() {
            nestedSortables = [].slice.call(document.querySelectorAll('.nested-sortable'));
            console.log(nestedSortables.length);

            for (var i = 0; i < nestedSortables.length; i++) {
                new Sortable(nestedSortables[i], {
                    group: 'nested',
                    animation: 150,
                    fallbackOnBody: true,
                    // invertSwap: true,
                    swapThreshold: 0.5
                });
            }

        }
    </script>
</body>

</html>

qomhmd avatar Dec 04 '24 15:12 qomhmd