progress in image viewing
authorPhiTux <redacted>
Tue, 11 Feb 2025 18:30:59 +0000 (19:30 +0100)
committerPhiTux <redacted>
Tue, 11 Feb 2025 18:30:59 +0000 (19:30 +0100)
backend/server/routers/logs.py
frontend/src/routes/write/+page.svelte

index 7027a2b2d31b600eeb7a8fdec505a784bad6f9fd..bb0e0b61a4042b033f4bdc467417d3d321d97ad3 100644 (file)
@@ -33,7 +33,7 @@ async def saveLog(log: Log, cookie = Depends(users.isLoggedIn)):
     # move old log to history
     if "days" in content.keys():
         for dayLog in content["days"]:
-            if dayLog["day"] == day:
+            if dayLog["day"] == day and "text" in dayLog.keys():
                 historyVersion = 0
                 if "history" not in dayLog.keys():
                     dayLog["history"] = []
@@ -92,6 +92,8 @@ async def getLog(date: str, cookie = Depends(users.isLoggedIn)):
     for dayLog in content["days"]:
         if dayLog["day"] == day:
             enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"])
+            text = ""
+            date_written = ""
             if "text" in dayLog.keys():
                 text = security.decrypt_text(dayLog["text"], enc_key)
                 date_written = security.decrypt_text(dayLog["date_written"], enc_key)
index f082f12e0a9f1f9e7b6cc4264dfcf6fe6e527dbb..7ecad18e03210fe9dbeef38b616a8a52e56e48d9 100644 (file)
@@ -46,6 +46,8 @@
                }
        );
 
+       let cancelDownload = new AbortController();
+
        let tinyMDE;
        onMount(() => {
                $readingDate = null; // no reading-highlighting when in write mode
@@ -83,6 +85,9 @@
                        images = [];
                        filesOfDay = [];
 
+                       cancelDownload.abort();
+                       cancelDownload = new AbortController();
+
                        clearTimeout(timeout);
                        const result = getLog();
                        if (result) {
                                        !images.find((image) => image.uuid_filename === file.uuid_filename)
                                ) {
                                        images = [...images, file];
+
                                        axios
-                                               .get(API_URL + '/logs/downloadFile', { params: { uuid: file.uuid_filename } })
+                                               .get(API_URL + '/logs/downloadFile', {
+                                                       params: { uuid: file.uuid_filename },
+                                                       signal: cancelDownload.signal
+                                               })
                                                .then((response) => {
                                                        images = images.map((image) => {
                                                                if (image.uuid_filename === file.uuid_filename) {
                                                        });
                                                })
                                                .catch((error) => {
+                                                       if (error.name == 'CanceledError') {
+                                                               return;
+                                                       }
+
                                                        console.error(error);
                                                        // toast
                                                        const toast = new bootstrap.Toast(document.getElementById('toastErrorLoadingFile'));
                return `${parseFloat((bytes / Math.pow(k, i)).toFixed(0))} ${sizes[i]}`;
        }
 
-       async function downloadFile(uuid) {
+       function downloadFile(uuid) {
                // check if present in filesOfDay
                let file = filesOfDay.find((file) => file.uuid_filename === uuid);
-               if (!file.src) {
-                       // download from server
+               if (file?.src) {
+                       triggerAutomaticDownload(uuid);
+                       return;
+               }
 
-                       try {
-                               const response = await axios.get(API_URL + '/logs/downloadFile', {
-                                       params: { uuid: uuid }
+               // download from server
+
+               filesOfDay = filesOfDay.map((f) => {
+                       if (f.uuid_filename === uuid) {
+                               f.downloadProgress = 0;
+                       }
+                       return f;
+               });
+
+               const config = {
+                       params: { uuid: uuid },
+                       onDownloadProgress: (progressEvent) => {
+                               filesOfDay = filesOfDay.map((file) => {
+                                       if (file.uuid_filename === uuid) {
+                                               file.downloadProgress = Math.round(progressEvent.progress * 100);
+                                       }
+                                       return file;
                                });
+                       },
+                       signal: cancelDownload.signal
+               };
 
+               axios
+                       .get(API_URL + '/logs/downloadFile', {
+                               ...config
+                       })
+                       .then((response) => {
                                filesOfDay = filesOfDay.map((f) => {
                                        if (f.uuid_filename === uuid) {
                                                f.src = response.data.file;
                                        }
                                        return f;
                                });
-                       } catch (error) {
+                       })
+                       .catch((error) => {
+                               if (error.name == 'CanceledError') {
+                                       return;
+                               }
+
                                console.error(error);
                                // toast
                                const toast = new bootstrap.Toast(document.getElementById('toastErrorLoadingFile'));
                                toast.show();
-                       }
-               }
+                       })
+                       .finally(() => {
+                               // remove progress
+                               filesOfDay = filesOfDay.map((f) => {
+                                       if (f.uuid_filename === uuid) {
+                                               f.downloadProgress = -1;
+                                       }
+                                       return f;
+                               });
+
+                               triggerAutomaticDownload(uuid);
+                       });
+       }
 
+       function triggerAutomaticDownload(uuid) {
+               let file;
                for (let i = 0; i < filesOfDay.length; i++) {
                        if (filesOfDay[i].uuid_filename === uuid) {
                                file = filesOfDay[i];
                        });
        }
 
-       let activeImage = $state('');
        function viewImage(uuid) {
-               activeImage = uuid;
+               // set active image
+               document.querySelectorAll('.carousel-item').forEach((item) => {
+                       item.classList.remove('active');
+                       if (
+                               item.id ===
+                               'carousel-item-' + images.findIndex((image) => image.uuid_filename === uuid)
+                       ) {
+                               item.classList.add('active');
+                       }
+               });
+               // set active image-button-indicator
+               document.querySelectorAll('.carousel-button').forEach((button) => {
+                       button.classList.remove('active');
+                       if (
+                               button.id ===
+                               'carousel-button-' + images.findIndex((image) => image.uuid_filename === uuid)
+                       ) {
+                               button.classList.add('active');
+                       }
+               });
 
                const modal = new bootstrap.Modal(document.getElementById('modalImages'));
                modal.show();
        </div>
 
        <!-- Center -->
-       <div class="d-flex flex-column mt-4 mx-4 flex-fill">
+       <div class="d-flex flex-column mt-4 mx-4 flex-fill" id="middle">
                <!-- Input-Area -->
-               <div class="d-flex flex-column">
-                       <div class="d-flex flex-row textAreaHeader">
-                               <div class="flex-fill textAreaDate">
-                                       {$selectedDate.toLocaleDateString('locale', { weekday: 'long' })}<br />
-                                       {$selectedDate.toLocaleDateString('locale', {
-                                               day: '2-digit',
-                                               month: '2-digit',
-                                               year: 'numeric'
-                                       })}
-                               </div>
-                               <div class="flex-fill textAreaWrittenAt">
-                                       <div class={logDateWritten ? '' : 'opacity-50'}>Geschrieben am:</div>
-                                       {logDateWritten}
-                               </div>
-                               <div class="textAreaHistory">history</div>
-                               <div class="textAreaDelete">delete</div>
+               <!-- <div class="d-flex flex-column"> -->
+               <div class="d-flex flex-row textAreaHeader">
+                       <div class="flex-fill textAreaDate">
+                               {$selectedDate.toLocaleDateString('locale', { weekday: 'long' })}<br />
+                               {$selectedDate.toLocaleDateString('locale', {
+                                       day: '2-digit',
+                                       month: '2-digit',
+                                       year: 'numeric'
+                               })}
                        </div>
-                       <div id="log" class="focus-ring">
-                               <div id="toolbar"></div>
-                               <div id="editor"></div>
+                       <div class="flex-fill textAreaWrittenAt">
+                               <div class={logDateWritten ? '' : 'opacity-50'}>Geschrieben am:</div>
+                               {logDateWritten}
                        </div>
-                       {#if images.length > 0}
-                               <div class="d-flex flex-row images mt-3">
-                                       {#each images as image (image.uuid_filename)}
-                                               <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
-                                                                       class="image"
-                                                                       alt={image.filename}
-                                                                       src={'data:image/jpg;base64,' + image.src}
-                                                               />
-                                                       {:else}
-                                                               <div class="spinner-border" role="status">
-                                                                       <span class="visually-hidden">Loading...</span>
-                                                               </div>
-                                                       {/if}
-                                               </button>
-                                       {/each}
-                               </div>
-                       {/if}
-                       {$selectedDate}<br />
-                       {lastSelectedDate}
+                       <div class="textAreaHistory">history</div>
+                       <div class="textAreaDelete">delete</div>
+               </div>
+               <div id="log" class="focus-ring">
+                       <div id="toolbar"></div>
+                       <div id="editor"></div>
                </div>
+               {#if images.length > 0}
+                       <div class="d-flex flex-row images mt-3">
+                               {#each images as image (image.uuid_filename)}
+                                       <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
+                                                               class="image"
+                                                               alt={image.filename}
+                                                               src={'data:image/' + image.filename.split('.').pop() + ';base64,' + image.src}
+                                                       />
+                                               {:else}
+                                                       <div class="spinner-border" role="status">
+                                                               <span class="visually-hidden">Loading...</span>
+                                                       </div>
+                                               {/if}
+                                       </button>
+                               {/each}
+                       </div>
+               {/if}
+               {$selectedDate}<br />
+               {lastSelectedDate}
+               <!-- </div> -->
        </div>
 
        <div id="right" class="d-flex flex-column">
                                <div class="btn-group file mt-2" transition:slide>
                                        <button
                                                onclick={() => downloadFile(file.uuid_filename)}
-                                               class="p-2 fileBtn d-flex flex-row align-items-center flex-fill"
-                                               ><div class="filename filenameWeight">{file.filename}</div>
-                                               <span class="filesize">({formatBytes(file.size)})</span>
+                                               class="p-2 fileBtn d-flex flex-column flex-fill"
+                                       >
+                                               <div class="d-flex flex-row align-items-center">
+                                                       <div class="filename filenameWeight">{file.filename}</div>
+                                                       <span class="filesize">({formatBytes(file.size)})</span>
+                                               </div>
+                                               {#if file.downloadProgress >= 0}
+                                                       <div
+                                                               class="progress"
+                                                               role="progressbar"
+                                                               aria-label="Download progress"
+                                                               aria-valuemin="0"
+                                                               aria-valuemax="100"
+                                                       >
+                                                               <div
+                                                                       class="progress-bar overflow-visible bg-info {file.downloadProgress === 0
+                                                                               ? 'progress-bar-striped progress-bar-animated'
+                                                                               : ''}"
+                                                                       style:width={file.downloadProgress + '%'}
+                                                                       aria-valuenow={file.downloadProgress}
+                                                                       aria-valuemax="100"
+                                                               >
+                                                                       {#if file.downloadProgress === 0}
+                                                                               <span class="text-dark">Wird entschlüsselt...</span>
+                                                                       {:else}
+                                                                               <span class="text-dark">Download: {file.downloadProgress}%</span>
+                                                                       {/if}
+                                                               </div>
+                                                       </div>
+                                               {/if}
                                        </button>
                                        <button
                                                class="p-2 fileBtn deleteFileBtn"
        >
                <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> -->
+                               <div class="modal-header d-none d-sm-flex">
                                        <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 id="imageCarousel" class="carousel slide carousel-fade">
                                                <div class="carousel-indicators">
                                                        {#each images as image, i (image.uuid_filename)}
                                                                <button
                                                                        data-bs-target="#imageCarousel"
                                                                        data-bs-slide-to={i}
                                                                        aria-label="Slide {i}"
-                                                                       class={image.uuid_filename === activeImage ? 'active' : ''}
+                                                                       class="carousel-button"
+                                                                       id="carousel-button-{i}"
                                                                ></button>
                                                        {/each}
                                                </div>
                                                <div class="carousel-inner">
-                                                       {#each images as image}
-                                                               <div class="carousel-item {image.uuid_filename === activeImage ? 'active' : ''}">
+                                                       {#each images as image, i (image.uuid_filename)}
+                                                               <div id="carousel-item-{i}" class="carousel-item">
                                                                        <img
                                                                                src={'data:image/' + image.filename.split('.').pop() + ';base64,' + image.src}
                                                                                class="d-block w-100"
 </div>
 
 <style>
+       .carousel-item > img {
+               transition: all ease 0.3s;
+       }
+
+       #middle {
+               min-width: 400px;
+       }
+
        .imageLabelCarousel {
                font-size: 20px;
                transition: background-color ease 0.3s;
                padding: 0px;
                border: 0px;
                background-color: transparent;
-               overflow: hidden;
+               overflow: clip;
        }
 
        .image:hover {
 
        .images {
                gap: 1rem;
+               overflow-x: auto;
        }
 
        :global(.modal.show) {
        .sidenav {
                /* max-width: 430px; */
                width: 380px;
+               min-width: 380px;
        }
 
        .textAreaHeader {
        }
 
        #right {
-               width: 300px;
+               min-width: 300px;
+               max-width: 400px;
        }
 </style>
git clone https://git.99rst.org/PROJECT