image info

This commit is contained in:
2026-04-02 09:49:20 -04:00
parent 27c3065582
commit b109efab98
7 changed files with 56 additions and 19 deletions
+4 -3
View File
@@ -351,6 +351,7 @@ const image = table(
id: t.u64().primaryKey().autoInc(),
data: t.byteArray(),
mime_type: t.string(),
name: t.string().optional(),
},
);
@@ -382,9 +383,9 @@ function validateName(name: string) {
}
export const upload_image = spacetimedb.reducer(
{ data: t.byteArray(), mimeType: t.string() },
(ctx, { data, mimeType }) => {
ctx.db.image.insert({ id: 0n, data, mime_type: mimeType });
{ data: t.byteArray(), mimeType: t.string(), name: t.string().optional() },
(ctx, { data, mimeType, name }) => {
ctx.db.image.insert({ id: 0n, data, mime_type: mimeType, name });
},
);
+35
View File
@@ -8,6 +8,12 @@
onClose();
}
}
const formatSize = (bytes: number) => {
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
return (bytes / (1024 * 1024)).toFixed(1) + " MB";
};
</script>
<svelte:window onkeydown={handleKeydown} />
@@ -15,6 +21,13 @@
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="image-viewer-overlay" onclick={onClose}>
<div class="image-viewer-info">
<div class="info-filename">{image.name || "Untitled Image"}</div>
<div class="info-details">
{image.mimeType}{formatSize(image.data.length)}
</div>
</div>
<button
class="image-viewer-close"
onclick={(e) => { e.stopPropagation(); onClose(); }}
@@ -40,12 +53,34 @@
bottom: 0;
background-color: rgba(0, 0, 0, 0.9);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 3000;
cursor: zoom-out;
}
.image-viewer-info {
position: absolute;
top: 20px;
left: 20px;
color: white;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
z-index: 3100;
pointer-events: none;
}
.info-filename {
font-weight: bold;
font-size: 1.1rem;
margin-bottom: 4px;
}
.info-details {
font-size: 0.85rem;
opacity: 0.8;
}
.image-viewer-content {
position: relative;
max-width: 90vw;
+5 -5
View File
@@ -16,7 +16,7 @@
const chat = getContext<ChatService>("chat");
let messageText = $state("");
let stagedImages = $state<{ data: Uint8Array; mimeType: string; previewUrl: string }[]>([]);
let stagedImages = $state<{ data: Uint8Array; mimeType: string; previewUrl: string; name: string }[]>([]);
async function handlePaste(e: ClipboardEvent) {
if (!isFullyAuthenticated) return;
@@ -29,16 +29,16 @@
if (!file) continue;
try {
const { data, mimeType } = await optimizeImage(file);
const { data, mimeType, name } = await optimizeImage(file);
const previewUrl = URL.createObjectURL(new Blob([data], { type: mimeType }));
stagedImages.push({ data, mimeType, previewUrl });
stagedImages.push({ data, mimeType, previewUrl, name });
} catch (err) {
console.error("Failed to optimize image:", err);
// Fallback to raw if optimization fails
const buffer = await file.arrayBuffer();
const data = new Uint8Array(buffer);
const previewUrl = URL.createObjectURL(new Blob([data], { type: file.type }));
stagedImages.push({ data, mimeType: file.type, previewUrl });
stagedImages.push({ data, mimeType: file.type, previewUrl, name: file.name });
}
}
}
@@ -63,7 +63,7 @@
const imageIds: bigint[] = [];
for (const staged of currentStaged) {
const beforeCount = chat.images.length;
chat.uploadImage(staged.data, staged.mimeType);
chat.uploadImage(staged.data, staged.mimeType, staged.name);
// Polling for the new image ID
const imageId = await new Promise<bigint>((resolve) => {
@@ -16,7 +16,7 @@
const chat = getContext<ChatService>("chat");
let threadMessageText = $state("");
let stagedImages = $state<{ data: Uint8Array; mimeType: string; previewUrl: string }[]>([]);
let stagedImages = $state<{ data: Uint8Array; mimeType: string; previewUrl: string; name: string }[]>([]);
async function handlePaste(e: ClipboardEvent) {
if (!isFullyAuthenticated) return;
@@ -29,15 +29,15 @@
if (!file) continue;
try {
const { data, mimeType } = await optimizeImage(file);
const { data, mimeType, name } = await optimizeImage(file);
const previewUrl = URL.createObjectURL(new Blob([data], { type: mimeType }));
stagedImages.push({ data, mimeType, previewUrl });
stagedImages.push({ data, mimeType, previewUrl, name });
} catch (err) {
console.error("Failed to optimize image:", err);
const buffer = await file.arrayBuffer();
const data = new Uint8Array(buffer);
const previewUrl = URL.createObjectURL(new Blob([data], { type: file.type }));
stagedImages.push({ data, mimeType: file.type, previewUrl });
stagedImages.push({ data, mimeType: file.type, previewUrl, name: file.name });
}
}
}
@@ -63,7 +63,7 @@
const imageIds: bigint[] = [];
for (const staged of currentStaged) {
const beforeCount = chat.images.length;
chat.uploadImage(staged.data, staged.mimeType);
chat.uploadImage(staged.data, staged.mimeType, staged.name);
const imageId = await new Promise<bigint>((resolve) => {
const checkInterval = setInterval(() => {
+2 -2
View File
@@ -304,8 +304,8 @@ export class ChatService {
this.#msg.toggleReaction(messageId, emoji);
};
uploadImage = async (data: Uint8Array, mimeType: string) => {
this.#msg.uploadImage(data, mimeType);
uploadImage = async (data: Uint8Array, mimeType: string, name?: string) => {
this.#msg.uploadImage(data, mimeType, name);
};
handleSendThreadMessage = (e: Event) => {
+2 -2
View File
@@ -78,8 +78,8 @@ export class MessagingService {
}
};
uploadImage = async (data: Uint8Array, mimeType: string) => {
this.#uploadImageReducer({ data, mimeType });
uploadImage = async (data: Uint8Array, mimeType: string, name?: string) => {
this.#uploadImageReducer({ data, mimeType, name });
};
toggleReaction = (messageId: bigint, emoji: string) => {
+3 -2
View File
@@ -18,7 +18,7 @@ export const formatTime = (ts: any) => {
return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
};
export const optimizeImage = async (file: File): Promise<{ data: Uint8Array, mimeType: string }> => {
export const optimizeImage = async (file: File): Promise<{ data: Uint8Array, mimeType: string, name: string }> => {
return new Promise((resolve, reject) => {
const img = new Image();
const reader = new FileReader();
@@ -60,7 +60,8 @@ export const optimizeImage = async (file: File): Promise<{ data: Uint8Array, mim
const buffer = await blob.arrayBuffer();
resolve({
data: new Uint8Array(buffer),
mimeType: "image/webp"
mimeType: "image/webp",
name: file.name
});
}, "image/webp", 0.8);
};