Compare commits

..

2 Commits

Author SHA1 Message Date
141ceab7e6 Reduce performance lost on large screens
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled
2026-03-09 16:41:55 +00:00
d03f9668ad Add error handling 2026-03-09 16:41:38 +00:00
4 changed files with 100 additions and 31 deletions

View File

@@ -18,8 +18,8 @@ var allowedExtensions = map[string]bool{
} }
var extensionToMIMEPrefix = map[string]string{ var extensionToMIMEPrefix = map[string]string{
".jpg": "image/", ".jpeg": "image/", ".png": "image/png", ".gif": "image/gif", ".webp": "image/webp", ".jpg": "image/", ".jpeg": "image/", ".png": "image/", ".gif": "image/", ".webp": "image/",
".mp4": "video/", ".webm": "video/webm", ".mp3": "audio/", ".ogg": "audio/", ".mp4": "video/", ".webm": "video/",
".pdf": "application/pdf", ".txt": "text/", ".pdf": "application/pdf", ".txt": "text/",
} }
@@ -49,8 +49,12 @@ func (store *Store) UploadMessageFile(ctx *gin.Context) {
return return
} }
buf := make([]byte, 512) buf := make([]byte, 512)
n, _ := f.Read(buf) n, err := f.Read(buf)
f.Close() f.Close()
if err != nil && n == 0 {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "failed to read file content"})
return
}
detectedType := http.DetectContentType(buf[:n]) detectedType := http.DetectContentType(buf[:n])
expectedPrefix, ok := extensionToMIMEPrefix[ext] expectedPrefix, ok := extensionToMIMEPrefix[ext]

View File

@@ -5,21 +5,31 @@ const container = useTemplateRef("container");
const item1 = useTemplateRef("item1"); const item1 = useTemplateRef("item1");
let offset = 0; let offset = 0;
let cachedWidth = 0;
let rafId; let rafId;
const speed = 0.5; // pixels per frame const speed = 0.5; // pixels per frame
function animate() { function measureWidth() {
const ctnr = container.value; const ctnr = container.value;
const it1 = item1.value; const it1 = item1.value;
if (ctnr && it1) {
cachedWidth = Math.max(ctnr.offsetWidth, it1.scrollWidth);
}
}
const width = Math.max(ctnr.offsetWidth, it1.scrollWidth); function animate() {
const ctnr = container.value;
if (!ctnr || cachedWidth === 0) {
rafId = requestAnimationFrame(animate);
return;
}
offset -= speed; offset -= speed;
if (offset <= -width) { if (offset <= -cachedWidth) {
offset += width; offset += cachedWidth;
} }
ctnr.style.transform = `translateX(${offset}px)`; ctnr.style.transform = `translateX(${offset}px)`;
@@ -27,12 +37,19 @@ function animate() {
rafId = requestAnimationFrame(animate); rafId = requestAnimationFrame(animate);
} }
let resizeObserver;
onMounted(() => { onMounted(() => {
measureWidth();
rafId = requestAnimationFrame(animate); rafId = requestAnimationFrame(animate);
resizeObserver = new ResizeObserver(measureWidth);
resizeObserver.observe(container.value);
}); });
onUnmounted(() => { onUnmounted(() => {
cancelAnimationFrame(rafId); cancelAnimationFrame(rafId);
resizeObserver?.disconnect();
}); });
</script> </script>

View File

@@ -16,6 +16,12 @@ let pos = 0;
let direction = 1; // 1 = down, -1 = up let direction = 1; // 1 = down, -1 = up
let timeoutId; let timeoutId;
let timeoutId2; let timeoutId2;
let cachedScrollHeight = 0;
function measureScrollHeight() {
const el = container.value;
if (el) cachedScrollHeight = el.scrollHeight;
}
function handleHover() { function handleHover() {
cancelAnimationFrame(timeoutId); cancelAnimationFrame(timeoutId);
@@ -28,6 +34,10 @@ function handleHover() {
function tick() { function tick() {
const el = container.value; const el = container.value;
if (!el || cachedScrollHeight === 0) {
timeoutId = requestAnimationFrame(tick);
return;
}
const reachedBottom = pos <= 0; const reachedBottom = pos <= 0;
const reachedTop = pos >= 1; const reachedTop = pos >= 1;
@@ -46,16 +56,23 @@ function tick() {
pos += direction * SPEED; pos += direction * SPEED;
el.scrollTop = pos * el.scrollHeight; el.scrollTop = pos * cachedScrollHeight;
timeoutId = requestAnimationFrame(tick); timeoutId = requestAnimationFrame(tick);
} }
let resizeObserver;
onMounted(() => { onMounted(() => {
measureScrollHeight();
timeoutId = requestAnimationFrame(tick); timeoutId = requestAnimationFrame(tick);
resizeObserver = new ResizeObserver(measureScrollHeight);
resizeObserver.observe(container.value);
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
cancelAnimationFrame(timeoutId); cancelAnimationFrame(timeoutId);
resizeObserver?.disconnect();
}); });
</script> </script>

View File

@@ -23,49 +23,83 @@ const phrases = [
"I like anime, all kinds of music and sci fic", "I like anime, all kinds of music and sci fic",
]; ];
const items = ref<Item[]>( // Non-reactive animation state to avoid triggering Vue re-renders every frame
phrases.map((text, i) => ({ const animState = phrases.map((text, i) => ({
x: i * 20, x: i * 20,
y: i * 20, y: i * 20,
dx: rand(0, 30) / 100, dx: rand(0, 30) / 100,
dy: 0.5, dy: 0.5,
content: text, content: text,
cachedW: 0,
cachedH: 0,
}));
// Reactive items only for initial render
const items = ref<Item[]>(
animState.map((s) => ({
x: s.x,
y: s.y,
dx: s.dx,
dy: s.dy,
content: s.content,
})), })),
); );
let rafId = 0; let rafId = 0;
let cachedCW = 0;
let cachedCH = 0;
function measureSizes() {
const c = container.value;
if (c) {
cachedCW = c.clientWidth;
cachedCH = c.clientHeight;
}
itemEls.value.forEach((el, i) => {
if (el && animState[i]) {
animState[i].cachedW = el.offsetWidth;
animState[i].cachedH = el.offsetHeight;
}
});
}
function animate() { function animate() {
const c = container.value; if (!cachedCW || !cachedCH) {
if (!c) return; rafId = requestAnimationFrame(animate);
return;
}
const cw = c.clientWidth; for (let i = 0; i < animState.length; i++) {
const ch = c.clientHeight; const s = animState[i];
items.value.forEach((item, i) => {
const el = itemEls.value[i]; const el = itemEls.value[i];
if (!el) return; if (!el) continue;
const ew = el.offsetWidth; s.x += s.dx;
const eh = el.offsetHeight; s.y += s.dy;
item.x += item.dx; if (s.x < 0 || s.x > cachedCW - s.cachedW) s.dx *= -1;
item.y += item.dy; if (s.y < 0 || s.y > cachedCH - s.cachedH) s.dy *= -1;
if (item.x < 0 || item.x > cw - ew) item.dx *= -1; el.style.transform = `translate(${s.x}px, ${s.y}px)`;
if (item.y < 0 || item.y > ch - eh) item.dy *= -1; }
});
rafId = requestAnimationFrame(animate); rafId = requestAnimationFrame(animate);
} }
let resizeObserver: ResizeObserver;
onMounted(async () => { onMounted(async () => {
await nextTick(); await nextTick();
measureSizes();
rafId = requestAnimationFrame(animate); rafId = requestAnimationFrame(animate);
resizeObserver = new ResizeObserver(measureSizes);
resizeObserver.observe(container.value!);
}); });
onUnmounted(() => { onUnmounted(() => {
cancelAnimationFrame(rafId); cancelAnimationFrame(rafId);
resizeObserver?.disconnect();
}); });
</script> </script>
@@ -79,9 +113,6 @@ onUnmounted(() => {
:key="i" :key="i"
ref="itemEls" ref="itemEls"
class="absolute w-fit h-fit" class="absolute w-fit h-fit"
:style="{
transform: `translate(${item.x}px, ${item.y}px)`,
}"
> >
<h1> <h1>
{{ item.content }} {{ item.content }}