Browse Source

Improve Blossom UI with thumbnails and full-width layout (v0.37.0)

- Make Blossom view use full available width
- Add "Upload new files" label with Select Files button on right
- Show image/video thumbnails in file list (48x48px)
- Add emoji icons for audio (🎵) and documents (📄)
- Show full hash on screens > 720px, truncated on smaller

Files modified:
- app/web/src/BlossomView.svelte: UI layout and thumbnail changes
- app/web/dist/*: Rebuilt bundle

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
main
mleku 3 weeks ago
parent
commit
aea8fd31e7
No known key found for this signature in database
  1. 2
      app/web/dist/bundle.css
  2. 24
      app/web/dist/bundle.js
  3. 2
      app/web/dist/bundle.js.map
  4. 79
      app/web/src/BlossomView.svelte
  5. 2
      pkg/version/version

2
app/web/dist/bundle.css vendored

File diff suppressed because one or more lines are too long

24
app/web/dist/bundle.js vendored

File diff suppressed because one or more lines are too long

2
app/web/dist/bundle.js.map vendored

File diff suppressed because one or more lines are too long

79
app/web/src/BlossomView.svelte

@ -159,10 +159,10 @@
function getMimeIcon(mimeType) { function getMimeIcon(mimeType) {
const category = getMimeCategory(mimeType); const category = getMimeCategory(mimeType);
switch (category) { switch (category) {
case "image": return ""; case "image": return "🖼";
case "video": return ""; case "video": return "🎬";
case "audio": return ""; case "audio": return "🎵";
default: return ""; default: return "📄";
} }
} }
@ -464,6 +464,7 @@
{#if !isAdminView && !selectedAdminUser} {#if !isAdminView && !selectedAdminUser}
<div class="upload-section"> <div class="upload-section">
<span class="upload-label">Upload new files</span>
<input <input
type="file" type="file"
multiple multiple
@ -471,9 +472,6 @@
on:change={handleFileSelect} on:change={handleFileSelect}
class="file-input-hidden" class="file-input-hidden"
/> />
<button class="select-btn" on:click={triggerFileInput} disabled={isUploading}>
Select Files
</button>
{#if selectedFiles.length > 0} {#if selectedFiles.length > 0}
<span class="selected-count">{selectedFiles.length} file(s) selected</span> <span class="selected-count">{selectedFiles.length} file(s) selected</span>
<button <button
@ -484,6 +482,9 @@
{isUploading ? uploadProgress : "Upload"} {isUploading ? uploadProgress : "Upload"}
</button> </button>
{/if} {/if}
<button class="select-btn" on:click={triggerFileInput} disabled={isUploading}>
Select Files
</button>
</div> </div>
{/if} {/if}
@ -552,12 +553,19 @@
role="button" role="button"
tabindex="0" tabindex="0"
> >
<div class="blob-icon"> <div class="blob-thumbnail">
{getMimeIcon(blob.type)} {#if getMimeCategory(blob.type) === "image"}
<img src={getBlobUrl(blob)} alt="" class="thumbnail-img" />
{:else if getMimeCategory(blob.type) === "video"}
<video src={getBlobUrl(blob)} class="thumbnail-video" muted preload="metadata"></video>
{:else}
<span class="thumbnail-icon">{getMimeIcon(blob.type)}</span>
{/if}
</div> </div>
<div class="blob-info"> <div class="blob-info">
<div class="blob-hash" title={blob.sha256}> <div class="blob-hash" title={blob.sha256}>
{truncateHash(blob.sha256)} <span class="hash-full">{blob.sha256}</span>
<span class="hash-truncated">{truncateHash(blob.sha256)}</span>
</div> </div>
<div class="blob-meta"> <div class="blob-meta">
<span class="blob-size">{formatSize(blob.size)}</span> <span class="blob-size">{formatSize(blob.size)}</span>
@ -678,7 +686,7 @@
<style> <style>
.blossom-view { .blossom-view {
padding: 1em; padding: 1em;
max-width: 900px; width: 100%;
} }
.header-section { .header-section {
@ -777,6 +785,12 @@
flex-wrap: wrap; flex-wrap: wrap;
} }
.upload-label {
color: var(--text-color);
font-size: 0.95em;
flex: 1;
}
.file-input-hidden { .file-input-hidden {
display: none; display: none;
} }
@ -861,10 +875,27 @@
background-color: var(--sidebar-bg); background-color: var(--sidebar-bg);
} }
.blob-icon { .blob-thumbnail {
width: 48px;
height: 48px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--bg-color);
border-radius: 4px;
overflow: hidden;
}
.thumbnail-img,
.thumbnail-video {
width: 100%;
height: 100%;
object-fit: cover;
}
.thumbnail-icon {
font-size: 1.5em; font-size: 1.5em;
width: 2em;
text-align: center;
} }
.blob-info { .blob-info {
@ -878,6 +909,14 @@
color: var(--text-color); color: var(--text-color);
} }
.hash-full {
display: inline;
}
.hash-truncated {
display: none;
}
.blob-meta { .blob-meta {
display: flex; display: flex;
gap: 1em; gap: 1em;
@ -1263,6 +1302,16 @@
color: var(--text-color); color: var(--text-color);
} }
@media (max-width: 720px) {
.hash-full {
display: none;
}
.hash-truncated {
display: inline;
}
}
@media (max-width: 600px) { @media (max-width: 600px) {
.blob-item { .blob-item {
flex-wrap: wrap; flex-wrap: wrap;
@ -1271,7 +1320,7 @@
.blob-date { .blob-date {
width: 100%; width: 100%;
margin-top: 0.5em; margin-top: 0.5em;
padding-left: 3em; padding-left: 3.5em;
} }
.modal-footer { .modal-footer {

2
pkg/version/version

@ -1 +1 @@
v0.36.23 v0.37.0

Loading…
Cancel
Save