Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
qomhmd opened this issue Dec 4, 2024 · 0 comments
Open

Comments

@qomhmd
Copy link

qomhmd commented Dec 4, 2024

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant