image/file download is working
authorPhiTux <redacted>
Fri, 31 Jan 2025 23:01:48 +0000 (00:01 +0100)
committerPhiTux <redacted>
Fri, 31 Jan 2025 23:01:48 +0000 (00:01 +0100)
frontend/src/routes/write/+page.svelte

index b662567f3c7f1eae6acc4e9be50dc8d153f2b845..f082f12e0a9f1f9e7b6cc4264dfcf6fe6e527dbb 100644 (file)
@@ -72,6 +72,7 @@
 
        let lastSelectedDate = $state($selectedDate);
        let images = $state([]);
+       let filesOfDay = $state([]);
 
        let loading = false;
        $effect(() => {
        let currentLog = $state('');
        let savedLog = $state('');
 
-       let filesOfDay = $state([]);
-
        let logDateWritten = $state('');
 
        let timeout;
                }
        }
 
-       const imageExtensions = ['jpeg', 'jpg', 'gif', 'png'];
+       const imageExtensions = ['jpeg', 'jpg', 'gif', 'png', 'webp'];
+       //TODO: support svg? -> minsize is necessary...
 
        function base64ToArrayBuffer(base64) {
                var binaryString = atob(base64);
                                                        images = images.map((image) => {
                                                                if (image.uuid_filename === file.uuid_filename) {
                                                                        image.src = response.data.file;
+                                                                       file.src = response.data.file;
                                                                }
                                                                return image;
                                                        });
                                                })
                                                .catch((error) => {
                                                        console.error(error);
+                                                       // toast
+                                                       const toast = new bootstrap.Toast(document.getElementById('toastErrorLoadingFile'));
+                                                       toast.show();
                                                });
                                }
                        });
                return `${parseFloat((bytes / Math.pow(k, i)).toFixed(0))} ${sizes[i]}`;
        }
 
-       function downloadFile(uuid) {
-               console.log(uuid);
+       async function downloadFile(uuid) {
+               // check if present in filesOfDay
+               let file = filesOfDay.find((file) => file.uuid_filename === uuid);
+               if (!file.src) {
+                       // download from server
+
+                       try {
+                               const response = await axios.get(API_URL + '/logs/downloadFile', {
+                                       params: { uuid: uuid }
+                               });
+
+                               filesOfDay = filesOfDay.map((f) => {
+                                       if (f.uuid_filename === uuid) {
+                                               f.src = response.data.file;
+                                       }
+                                       return f;
+                               });
+                       } catch (error) {
+                               console.error(error);
+                               // toast
+                               const toast = new bootstrap.Toast(document.getElementById('toastErrorLoadingFile'));
+                               toast.show();
+                       }
+               }
+
+               for (let i = 0; i < filesOfDay.length; i++) {
+                       if (filesOfDay[i].uuid_filename === uuid) {
+                               file = filesOfDay[i];
+                               break;
+                       }
+               }
+
+               const blob = new Blob([base64ToArrayBuffer(file.src)], {
+                       type: 'application/octet-stream'
+               });
+               const url = window.URL.createObjectURL(blob);
+               const a = document.createElement('a');
+               a.href = url;
+               a.download = file.filename;
+               document.body.appendChild(a);
+               a.click();
        }
 
        let confirmDelete = $state({ uuid: '', filename: '' });
                                toast.show();
                        });
        }
+
+       let activeImage = $state('');
+       function viewImage(uuid) {
+               activeImage = uuid;
+
+               const modal = new bootstrap.Modal(document.getElementById('modalImages'));
+               modal.show();
+       }
 </script>
 
 <DatepickerLogic />
                        {#if images.length > 0}
                                <div class="d-flex flex-row images mt-3">
                                        {#each images as image (image.uuid_filename)}
-                                               <div class="imageContainer d-flex align-items-center" transition:slide={{ axis: 'x' }}>
+                                               <button
+                                                       type="button"
+                                                       onclick={() => {
+                                                               viewImage(image.uuid_filename);
+                                                       }}
+                                                       class="imageContainer d-flex align-items-center position-relative"
+                                                       transition:slide={{ axis: 'x' }}
+                                               >
                                                        {#if image.src}
                                                                <img
                                                                        transition:fade
                                                                        <span class="visually-hidden">Loading...</span>
                                                                </div>
                                                        {/if}
-                                               </div>
+                                               </button>
                                        {/each}
                                </div>
                        {/if}
 
                <div class="files d-flex flex-column">
                        <button
-                               class="btn btn-secondary {filesOfDay.length > 0 ? 'mb-2' : ''}"
+                               class="btn btn-secondary {filesOfDay?.length > 0 ? 'mb-2' : ''}"
                                id="uploadBtn"
                                onclick={triggerFileInput}
                                ><Fa icon={faCloudArrowUp} class="me-2" id="uploadIcon" />Upload</button
                                <div class="toast-body">Fehler beim Löschen einer Datei!</div>
                        </div>
                </div>
+
+               <div
+                       id="toastErrorLoadingFile"
+                       class="toast align-items-center text-bg-danger"
+                       role="alert"
+                       aria-live="assertive"
+                       aria-atomic="true"
+               >
+                       <div class="d-flex">
+                               <div class="toast-body">Fehler beim Download einer Datei!</div>
+                       </div>
+               </div>
        </div>
 
        <div class="modal fade" id="modalConfirmDeleteFile" tabindex="-1">
                        </div>
                </div>
        </div>
+
+       <div
+               class="modal fade"
+               id="modalImages"
+               tabindex="-1"
+               aria-labelledby="modalImagesLabel"
+               aria-hidden="true"
+       >
+               <div class="modal-dialog modal-xl modal-fullscreen-sm-down">
+                       <div class="modal-content">
+                               <div class="modal-header d-none d-sm-block">
+                                       <!-- <h1 class="modal-title fs-5" id="exampleModalLabel"></h1> -->
+                                       <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"
+                                       ></button>
+                               </div>
+
+                               <div class="modal-body">
+                                       <div id="imageCarousel" class="carousel slide">
+                                               <div class="carousel-indicators">
+                                                       {#each images as image, i (image.uuid_filename)}
+                                                               <button
+                                                                       type="button"
+                                                                       data-bs-target="#imageCarousel"
+                                                                       data-bs-slide-to={i}
+                                                                       aria-label="Slide {i}"
+                                                                       class={image.uuid_filename === activeImage ? 'active' : ''}
+                                                               ></button>
+                                                       {/each}
+                                               </div>
+                                               <div class="carousel-inner">
+                                                       {#each images as image}
+                                                               <div class="carousel-item {image.uuid_filename === activeImage ? 'active' : ''}">
+                                                                       <img
+                                                                               src={'data:image/' + image.filename.split('.').pop() + ';base64,' + image.src}
+                                                                               class="d-block w-100"
+                                                                               alt={image.filename}
+                                                                       />
+                                                                       <div class="carousel-caption d-none d-md-block">
+                                                                               <span class="imageLabelCarousel">{image.filename}</span>
+                                                                               <button
+                                                                                       class="btn btn-primary"
+                                                                                       onclick={() => downloadFile(image.uuid_filename)}
+                                                                               >
+                                                                                       Download
+                                                                               </button>
+                                                                       </div>
+                                                               </div>
+                                                       {/each}
+                                               </div>
+                                               <button
+                                                       class="carousel-control-prev"
+                                                       type="button"
+                                                       data-bs-target="#imageCarousel"
+                                                       data-bs-slide="prev"
+                                               >
+                                                       <span class="carousel-control-prev-icon" aria-hidden="true"></span>
+                                                       <span class="visually-hidden">Previous</span>
+                                               </button>
+                                               <button
+                                                       class="carousel-control-next"
+                                                       type="button"
+                                                       data-bs-target="#imageCarousel"
+                                                       data-bs-slide="next"
+                                               >
+                                                       <span class="carousel-control-next-icon" aria-hidden="true"></span>
+                                                       <span class="visually-hidden">Next</span>
+                                               </button>
+                                       </div>
+                               </div>
+                               <div class="modal-footer d-block d-sm-none">
+                                       <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
+                               </div>
+                       </div>
+               </div>
+       </div>
 </div>
 
 <style>
+       .imageLabelCarousel {
+               font-size: 20px;
+               transition: background-color ease 0.3s;
+               padding: 5px;
+               border-radius: 5px;
+       }
+
+       .carousel-caption:hover > .imageLabelCarousel {
+               background-color: rgba(0, 0, 0, 0.4);
+       }
+
        .image,
        .imageContainer {
                border-radius: 8px;
 
        .imageContainer {
                min-height: 80px;
+               padding: 0px;
+               border: 0px;
+               background-color: transparent;
+               overflow: hidden;
        }
 
        .image:hover {
git clone https://git.99rst.org/PROJECT