From: PhiTux Date: Thu, 27 Mar 2025 18:30:00 +0000 (+0100) Subject: show files and images in reading-mode X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=822ae59b2a9ad91b30d6e6aa0a0a0c6d8b03afd0;p=DailyTxT.git show files and images in reading-mode --- diff --git a/backend/server/routers/logs.py b/backend/server/routers/logs.py index 0c54f6f..2616e0e 100644 --- a/backend/server/routers/logs.py +++ b/backend/server/routers/logs.py @@ -277,10 +277,21 @@ async def loadMonthForReading(month: int, year: int, cookie = Depends(users.isLo days = [] enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"]) for dayLog in content["days"]: + day = {"day": dayLog["day"]} if "text" in dayLog.keys(): - days.append({"day": dayLog["day"], - "text": security.decrypt_text(dayLog["text"], enc_key), - "date_written": security.decrypt_text(dayLog["date_written"], enc_key)}) + day["text"] = security.decrypt_text(dayLog["text"], enc_key) + day["date_written"] = security.decrypt_text(dayLog["date_written"], enc_key) + if "tags" in dayLog.keys(): + day["tags"] = dayLog["tags"] + if "files" in dayLog.keys(): + day["files"] = [] + for file in dayLog["files"]: + file["filename"] = security.decrypt_text(file["enc_filename"], enc_key) + day["files"].append(file) + + # if one of the keys is in day: + if "text" in day or "files" in day or "tags" in day: + days.append(day) days.sort(key=lambda x: x["day"]) diff --git a/frontend/src/lib/FileList.svelte b/frontend/src/lib/FileList.svelte new file mode 100644 index 0000000..0b6be36 --- /dev/null +++ b/frontend/src/lib/FileList.svelte @@ -0,0 +1,90 @@ + + +{#each files as file (file.uuid_filename)} +
+ + {#if deleteAllowed} + + {/if} +
+{/each} + + diff --git a/frontend/src/lib/helpers.js b/frontend/src/lib/helpers.js new file mode 100644 index 0000000..731e3b0 --- /dev/null +++ b/frontend/src/lib/helpers.js @@ -0,0 +1,13 @@ +function formatBytes(bytes) { + if (!+bytes) return '0 Bytes'; + + const k = 1024; + //const dm = 2; // decimal places + const sizes = ['B', 'KB', 'MB', 'GB']; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(0))} ${sizes[i]}`; +} + +export { formatBytes }; diff --git a/frontend/src/routes/read/+page.svelte b/frontend/src/routes/read/+page.svelte index 0aa4c48..f599d69 100644 --- a/frontend/src/routes/read/+page.svelte +++ b/frontend/src/routes/read/+page.svelte @@ -6,6 +6,13 @@ import Sidenav from '$lib/Sidenav.svelte'; import { onMount } from 'svelte'; import { marked } from 'marked'; + import Tag from '$lib/Tag.svelte'; + import { tags } from '$lib/tagStore.js'; + import FileList from '$lib/FileList.svelte'; + import { autoLoadImages } from '$lib/settingsStore'; + import { faCloudArrowDown } from '@fortawesome/free-solid-svg-icons'; + import { Fa } from 'svelte-fa'; + import { fade, slide } from 'svelte/transition'; marked.use({ breaks: true, @@ -45,6 +52,9 @@ let currentYear = $cal.currentYear; $effect(() => { if ($cal.currentMonth !== currentMonth || $cal.currentYear !== currentYear) { + cancelDownload.abort(); + cancelDownload = new AbortController(); + loadMonthForReading(); currentMonth = $cal.currentMonth; currentYear = $cal.currentYear; @@ -60,8 +70,213 @@ } }); - //#TODO Muss in die separate /read page (diese hier in /write umbenennen) + const imageExtensions = ['jpeg', 'jpg', 'gif', 'png', 'webp']; + //TODO: support svg? -> minsize is necessary... + + // copy of files, which are images + $effect(() => { + if (logs) { + logs.forEach((log) => { + if (log.files) { + if (!log.images) { + log.images = []; + } + + log.files.forEach((file) => { + if ( + imageExtensions.includes(file.filename.split('.').pop().toLowerCase()) && + !log.images.find((image) => image.uuid_filename === file.uuid_filename) + ) { + log.images = [...log.images, file]; + + if ($autoLoadImages) { + loadImage(file.uuid_filename); + } + } + }); + } + }); + } + }); + + function loadImage(uuid) { + for (let i = 0; i < logs.length; i++) { + let log = logs[i]; + + // skip log if file not in this day/log + if (!log.images) { + continue; + } + let image = log.images.find((image) => image.uuid_filename === uuid); + if (!image) { + continue; + } + + log.images = log.images.map((image) => { + if (image.uuid_filename === uuid) { + image.loading = true; + } + return image; + }); + + axios + .get(API_URL + '/logs/downloadFile', { + params: { uuid: uuid }, + responseType: 'blob', + signal: cancelDownload.signal + }) + .then((response) => { + const url = URL.createObjectURL(new Blob([response.data])); + log.images = log.images.map((image) => { + if (image.uuid_filename === uuid) { + image.src = url; + image.loading = false; + } + return image; + }); + + log.files = log.files.map((file) => { + if (file.uuid_filename === uuid) { + file.src = url; + } + return file; + }); + }) + .catch((error) => { + if (error.name == 'CanceledError') { + return; + } + + console.error(error); + // toast + const toast = new bootstrap.Toast(document.getElementById('toastErrorLoadingFile')); + toast.show(); + }); + } + } + + function loadImages() { + for (let i = 0; i < logs.length; i++) { + let log = logs[i]; + + // skip log if no images in this day/log + if (!log.images) { + continue; + } + + log.images.forEach((image) => { + if (!image.src) { + loadImage(image.uuid_filename); + } + }); + } + } + + let cancelDownload = new AbortController(); + + function downloadFile(uuid) { + for (let i = 0; i < logs.length; i++) { + let log = logs[i]; + + // skip log if file not in this day/log + if (!log.files) { + continue; + } + let file = log.files.find((file) => file.uuid_filename === uuid); + if (!file) { + continue; + } + + // check if src is present in files + if (file.src) { + triggerAutomaticDownload(uuid); + return; + } + + // otherwise: download from server + log.files = log.files.map((f) => { + if (f.uuid_filename === uuid) { + f.downloadProgress = 0; + } + return f; + }); + + const config = { + params: { uuid: uuid }, + onDownloadProgress: (progressEvent) => { + log.files = log.files.map((file) => { + if (file.uuid_filename === uuid) { + file.downloadProgress = Math.round((progressEvent.loaded / file.size) * 100); + } + return file; + }); + }, + signal: cancelDownload.signal, + responseType: 'blob' + }; + + axios + .get(API_URL + '/logs/downloadFile', { + ...config + }) + .then((response) => { + const url = URL.createObjectURL(new Blob([response.data])); + log.files = log.files.map((f) => { + if (f.uuid_filename === uuid) { + f.src = url; + } + return f; + }); + }) + .catch((error) => { + if (error.name == 'CanceledError') { + return; + } + + console.error(error); + // toast + const toast = new bootstrap.Toast(document.getElementById('toastErrorLoadingFile')); + toast.show(); + }) + .finally(() => { + // remove progress + log.files = log.files.map((f) => { + if (f.uuid_filename === uuid) { + f.downloadProgress = -1; + } + return f; + }); + + triggerAutomaticDownload(uuid); + }); + } + } + + //#TODO Anpassen + function triggerAutomaticDownload(uuid) { + for (let i = 0; i < logs.length; i++) { + let log = logs[i]; + + // skip log if file not in this day/log + if (!log.files) { + continue; + } + let file = log.files.find((file) => file.uuid_filename === uuid); + if (!file) { + continue; + } + + const a = document.createElement('a'); + a.href = file.src; + a.download = file.filename; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + } + } + let isLoadingMonthForReading = false; + function loadMonthForReading() { if (isLoadingMonthForReading) { return; @@ -116,8 +331,8 @@ -
- {#each logs as log} +
+ {#each logs as log (log.day)}
@@ -130,17 +345,113 @@

-
- {@html marked.parse(log.text)} +
+ {#if log.text && log.text !== ''} +
+ {@html marked.parse(log.text)} +
+ {/if} + {#if log.tags?.length > 0} +
+ {#each log.tags as t} + tag.id === t)} /> + {/each} +
+ {/if} + {#if log.images?.length > 0} + {#if !$autoLoadImages && log.images.find((image) => !image.src && !image.loading)} +
+ +
+ {:else} +
+ {#each log.images as image (image.uuid_filename)} + + {/each} +
+ {/if} + {/if}
+ + {#if log.files && log.files.length > 0} +
+ +
+ {/if}
{/each}
- -
diff --git a/frontend/src/routes/write/+page.svelte b/frontend/src/routes/write/+page.svelte index c834b84..61ec8a4 100644 --- a/frontend/src/routes/write/+page.svelte +++ b/frontend/src/routes/write/+page.svelte @@ -14,7 +14,6 @@ import { faCloudArrowUp, faCloudArrowDown, - faTrash, faSquarePlus, faQuestionCircle } from '@fortawesome/free-solid-svg-icons'; @@ -25,6 +24,8 @@ import { tags } from '$lib/tagStore'; import Tag from '$lib/Tag.svelte'; import TagModal from '$lib/TagModal.svelte'; + import FileList from '$lib/FileList.svelte'; + import { formatBytes } from '$lib/helpers.js'; axios.interceptors.request.use((config) => { config.withCredentials = true; @@ -457,18 +458,6 @@ }); } - function formatBytes(bytes) { - if (!+bytes) return '0 Bytes'; - - const k = 1024; - //const dm = 2; // decimal places - const sizes = ['B', 'KB', 'MB', 'GB']; - - const i = Math.floor(Math.log(bytes) / Math.log(k)); - - return `${parseFloat((bytes / Math.pow(k, i)).toFixed(0))} ${sizes[i]}`; - } - function downloadFile(uuid) { // check if present in filesOfDay let file = filesOfDay.find((file) => file.uuid_filename === uuid); @@ -492,7 +481,6 @@ filesOfDay = filesOfDay.map((file) => { if (file.uuid_filename === uuid) { file.downloadProgress = Math.round((progressEvent.loaded / file.size) * 100); - console.log(progressEvent); } return file; }); @@ -550,7 +538,6 @@ a.href = file.src; a.download = file.filename; document.body.appendChild(a); - console.log(a); a.click(); document.body.removeChild(a); } @@ -851,15 +838,18 @@
{#if images.length > 0} - {#if !$autoLoadImages && !images.find((image) => image.src || image.loading)} + {#if !$autoLoadImages && images.find((image) => !image.src && !image.loading)}
-
{:else} @@ -975,48 +965,7 @@ > - {#each filesOfDay as file (file.uuid_filename)} -
- - -
- {/each} + {#each uploadingFiles as file}
{file.name} @@ -1311,7 +1260,7 @@ border-radius: 10px; } - #loadImageBtn { + .loadImageBtn { padding: 0.5rem 1rem; border: none; margin-top: 0.5rem; @@ -1381,41 +1330,6 @@ font-weight: 550; } - .filename { - padding-right: 0.5rem; - word-break: break-word; - } - - .filesize { - opacity: 0.7; - font-size: 0.8rem; - white-space: nowrap; - } - - .fileBtn { - border: 0; - background-color: rgba(0, 0, 0, 0); - transition: all ease 0.3s; - } - - .fileBtn:hover { - background-color: rgba(0, 0, 0, 0.1); - } - - .deleteFileBtn { - border-left: 1px solid rgba(92, 92, 92, 0.445); - } - - .deleteFileBtn:hover { - color: rgb(165, 0, 0); - } - - .file { - background-color: rgba(117, 117, 117, 0.45); - border: 0px solid #ececec77; - border-radius: 5px; - } - .files { margin-right: 2rem; border-radius: 10px;