image info
This commit is contained in:
@@ -351,6 +351,7 @@ const image = table(
|
|||||||
id: t.u64().primaryKey().autoInc(),
|
id: t.u64().primaryKey().autoInc(),
|
||||||
data: t.byteArray(),
|
data: t.byteArray(),
|
||||||
mime_type: t.string(),
|
mime_type: t.string(),
|
||||||
|
name: t.string().optional(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -382,9 +383,9 @@ function validateName(name: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const upload_image = spacetimedb.reducer(
|
export const upload_image = spacetimedb.reducer(
|
||||||
{ data: t.byteArray(), mimeType: t.string() },
|
{ data: t.byteArray(), mimeType: t.string(), name: t.string().optional() },
|
||||||
(ctx, { data, mimeType }) => {
|
(ctx, { data, mimeType, name }) => {
|
||||||
ctx.db.image.insert({ id: 0n, data, mime_type: mimeType });
|
ctx.db.image.insert({ id: 0n, data, mime_type: mimeType, name });
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,12 @@
|
|||||||
onClose();
|
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>
|
</script>
|
||||||
|
|
||||||
<svelte:window onkeydown={handleKeydown} />
|
<svelte:window onkeydown={handleKeydown} />
|
||||||
@@ -15,6 +21,13 @@
|
|||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div class="image-viewer-overlay" onclick={onClose}>
|
<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
|
<button
|
||||||
class="image-viewer-close"
|
class="image-viewer-close"
|
||||||
onclick={(e) => { e.stopPropagation(); onClose(); }}
|
onclick={(e) => { e.stopPropagation(); onClose(); }}
|
||||||
@@ -40,12 +53,34 @@
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
background-color: rgba(0, 0, 0, 0.9);
|
background-color: rgba(0, 0, 0, 0.9);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 3000;
|
z-index: 3000;
|
||||||
cursor: zoom-out;
|
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 {
|
.image-viewer-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
max-width: 90vw;
|
max-width: 90vw;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
const chat = getContext<ChatService>("chat");
|
const chat = getContext<ChatService>("chat");
|
||||||
|
|
||||||
let messageText = $state("");
|
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) {
|
async function handlePaste(e: ClipboardEvent) {
|
||||||
if (!isFullyAuthenticated) return;
|
if (!isFullyAuthenticated) return;
|
||||||
@@ -29,16 +29,16 @@
|
|||||||
if (!file) continue;
|
if (!file) continue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data, mimeType } = await optimizeImage(file);
|
const { data, mimeType, name } = await optimizeImage(file);
|
||||||
const previewUrl = URL.createObjectURL(new Blob([data], { type: mimeType }));
|
const previewUrl = URL.createObjectURL(new Blob([data], { type: mimeType }));
|
||||||
stagedImages.push({ data, mimeType, previewUrl });
|
stagedImages.push({ data, mimeType, previewUrl, name });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to optimize image:", err);
|
console.error("Failed to optimize image:", err);
|
||||||
// Fallback to raw if optimization fails
|
// Fallback to raw if optimization fails
|
||||||
const buffer = await file.arrayBuffer();
|
const buffer = await file.arrayBuffer();
|
||||||
const data = new Uint8Array(buffer);
|
const data = new Uint8Array(buffer);
|
||||||
const previewUrl = URL.createObjectURL(new Blob([data], { type: file.type }));
|
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[] = [];
|
const imageIds: bigint[] = [];
|
||||||
for (const staged of currentStaged) {
|
for (const staged of currentStaged) {
|
||||||
const beforeCount = chat.images.length;
|
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
|
// Polling for the new image ID
|
||||||
const imageId = await new Promise<bigint>((resolve) => {
|
const imageId = await new Promise<bigint>((resolve) => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
const chat = getContext<ChatService>("chat");
|
const chat = getContext<ChatService>("chat");
|
||||||
|
|
||||||
let threadMessageText = $state("");
|
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) {
|
async function handlePaste(e: ClipboardEvent) {
|
||||||
if (!isFullyAuthenticated) return;
|
if (!isFullyAuthenticated) return;
|
||||||
@@ -29,15 +29,15 @@
|
|||||||
if (!file) continue;
|
if (!file) continue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data, mimeType } = await optimizeImage(file);
|
const { data, mimeType, name } = await optimizeImage(file);
|
||||||
const previewUrl = URL.createObjectURL(new Blob([data], { type: mimeType }));
|
const previewUrl = URL.createObjectURL(new Blob([data], { type: mimeType }));
|
||||||
stagedImages.push({ data, mimeType, previewUrl });
|
stagedImages.push({ data, mimeType, previewUrl, name });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to optimize image:", err);
|
console.error("Failed to optimize image:", err);
|
||||||
const buffer = await file.arrayBuffer();
|
const buffer = await file.arrayBuffer();
|
||||||
const data = new Uint8Array(buffer);
|
const data = new Uint8Array(buffer);
|
||||||
const previewUrl = URL.createObjectURL(new Blob([data], { type: file.type }));
|
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[] = [];
|
const imageIds: bigint[] = [];
|
||||||
for (const staged of currentStaged) {
|
for (const staged of currentStaged) {
|
||||||
const beforeCount = chat.images.length;
|
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 imageId = await new Promise<bigint>((resolve) => {
|
||||||
const checkInterval = setInterval(() => {
|
const checkInterval = setInterval(() => {
|
||||||
|
|||||||
@@ -304,8 +304,8 @@ export class ChatService {
|
|||||||
this.#msg.toggleReaction(messageId, emoji);
|
this.#msg.toggleReaction(messageId, emoji);
|
||||||
};
|
};
|
||||||
|
|
||||||
uploadImage = async (data: Uint8Array, mimeType: string) => {
|
uploadImage = async (data: Uint8Array, mimeType: string, name?: string) => {
|
||||||
this.#msg.uploadImage(data, mimeType);
|
this.#msg.uploadImage(data, mimeType, name);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSendThreadMessage = (e: Event) => {
|
handleSendThreadMessage = (e: Event) => {
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ export class MessagingService {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
uploadImage = async (data: Uint8Array, mimeType: string) => {
|
uploadImage = async (data: Uint8Array, mimeType: string, name?: string) => {
|
||||||
this.#uploadImageReducer({ data, mimeType });
|
this.#uploadImageReducer({ data, mimeType, name });
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleReaction = (messageId: bigint, emoji: string) => {
|
toggleReaction = (messageId: bigint, emoji: string) => {
|
||||||
|
|||||||
+3
-2
@@ -18,7 +18,7 @@ export const formatTime = (ts: any) => {
|
|||||||
return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
@@ -60,7 +60,8 @@ export const optimizeImage = async (file: File): Promise<{ data: Uint8Array, mim
|
|||||||
const buffer = await blob.arrayBuffer();
|
const buffer = await blob.arrayBuffer();
|
||||||
resolve({
|
resolve({
|
||||||
data: new Uint8Array(buffer),
|
data: new Uint8Array(buffer),
|
||||||
mimeType: "image/webp"
|
mimeType: "image/webp",
|
||||||
|
name: file.name
|
||||||
});
|
});
|
||||||
}, "image/webp", 0.8);
|
}, "image/webp", 0.8);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user