mirror of
https://github.com/logseq/logseq.git
synced 2026-02-01 22:47:36 +00:00
Add delete button and Selected section to asset picker
- Add delete button in topbar (matching icon-picker styling with red hover) - Add "Selected" section showing currently chosen image with blue outline - Add :simple? option to section-header to hide count/chevron - Floating action buttons now use gradient fade instead of solid background - Use object-fit: cover for avatar mode, contain for regular images - Symmetric 8px padding in pane-section Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -732,19 +732,21 @@
|
||||
(defonce *section-states (atom {}))
|
||||
|
||||
(rum/defc section-header
|
||||
[{:keys [title count total-count expanded? keyboard-hint on-toggle input-focused?]}]
|
||||
[{:keys [title count total-count expanded? keyboard-hint on-toggle input-focused? simple?]}]
|
||||
[:div.section-header.text-xs.py-1.5.px-3.flex.justify-between.items-center.gap-2.bg-gray-02.h-8
|
||||
{:style {:color "var(--lx-gray-11)"}}
|
||||
;; Left: Title · total-count · Chevron
|
||||
[:div.flex.items-center.gap-1.cursor-pointer.select-none
|
||||
{:on-click on-toggle}
|
||||
;; Left: Title · total-count · Chevron (chevron and count hidden in simple mode)
|
||||
[:div.flex.items-center.gap-1.select-none
|
||||
(when-not simple? {:class "cursor-pointer"
|
||||
:on-click on-toggle})
|
||||
[:span.font-bold title]
|
||||
(when (or total-count count)
|
||||
(when (and (not simple?) (or total-count count))
|
||||
[:<>
|
||||
[:span "·"]
|
||||
[:span {:style {:font-size "0.7rem"}}
|
||||
(or total-count count)]])
|
||||
(ui/icon (if expanded? "chevron-down" "chevron-right") {:size 14})]
|
||||
(when-not simple?
|
||||
(ui/icon (if expanded? "chevron-down" "chevron-right") {:size 14}))]
|
||||
|
||||
[:div.flex-1] ; Spacer
|
||||
|
||||
@@ -1041,7 +1043,7 @@
|
||||
"Renders a single image asset thumbnail in the asset picker grid.
|
||||
When avatar-context is provided, renders circular previews and returns avatar data.
|
||||
Returns nil if asset file doesn't exist (ghost asset)."
|
||||
[state asset {:keys [on-chosen avatar-context]}]
|
||||
[state asset {:keys [on-chosen avatar-context selected?]}]
|
||||
(let [url @(::url state)
|
||||
error? @(::error state)
|
||||
asset-type (:logseq.property.asset/type asset)
|
||||
@@ -1052,7 +1054,8 @@
|
||||
(when-not error?
|
||||
[:button.image-asset-item
|
||||
{:title asset-title
|
||||
:class (when avatar-mode? "avatar-mode")
|
||||
:class (util/classnames [{:avatar-mode avatar-mode?
|
||||
:selected selected?}])
|
||||
:on-click (fn [e]
|
||||
(let [image-data {:asset-uuid (str asset-uuid)
|
||||
:asset-type asset-type}]
|
||||
@@ -1094,7 +1097,7 @@
|
||||
(p/catch (fn [_err]
|
||||
(reset! *loading? false))))))
|
||||
state)}
|
||||
[state {:keys [on-chosen on-back avatar-context]}]
|
||||
[state {:keys [on-chosen on-back on-delete del-btn? current-icon avatar-context]}]
|
||||
(let [*search-q (::search-q state)
|
||||
*loading? (::loading? state)
|
||||
*loaded-assets (::loaded-assets state)
|
||||
@@ -1102,6 +1105,14 @@
|
||||
;; Use cached assets if available, otherwise try to get them
|
||||
assets (or (rum/react *loaded-assets) [])
|
||||
search-q @*search-q
|
||||
;; Extract current image UUID from the icon (works for both :image and :avatar with image)
|
||||
current-asset-uuid (or (get-in current-icon [:data :asset-uuid])
|
||||
(when (= :image (:type current-icon))
|
||||
(get-in current-icon [:data :asset-uuid])))
|
||||
;; Find the current asset from the list
|
||||
current-asset (when current-asset-uuid
|
||||
(some #(when (= (str (:block/uuid %)) current-asset-uuid) %)
|
||||
assets))
|
||||
;; Filter assets by search query
|
||||
filtered-assets (if (string/blank? search-q)
|
||||
assets
|
||||
@@ -1150,7 +1161,12 @@
|
||||
[:button.back-button
|
||||
{:on-click on-back}
|
||||
(shui/tabler-icon "chevron-left" {:size 16})
|
||||
[:span "Back"]]]
|
||||
[:span "Back"]]
|
||||
;; Delete button (aligned to right)
|
||||
(when del-btn?
|
||||
(shui/button {:variant :outline :size :sm :data-action "del"
|
||||
:on-click on-delete}
|
||||
(shui/tabler-icon "trash" {:size 17})))]
|
||||
(shui/separator {:class "my-0 opacity-50"})
|
||||
[:div.asset-picker-search
|
||||
[:div.search-input
|
||||
@@ -1161,33 +1177,47 @@
|
||||
:auto-focus true
|
||||
:on-change #(reset! *search-q (util/evalue %))})]]]
|
||||
|
||||
;; Section header (matching icon picker style)
|
||||
[:div.asset-picker-section-header
|
||||
[:div.section-title
|
||||
[:span.font-bold "Images"]
|
||||
[:span.font-medium (str " · " asset-count)]]]
|
||||
;; Body - scrollable content area with top/bottom margin
|
||||
[:div.bd.bd-scroll
|
||||
;; "Current" section - shows currently selected image (only when not searching)
|
||||
(when (and current-asset (string/blank? search-q))
|
||||
[:div.pane-section
|
||||
(section-header {:title "Selected"
|
||||
:simple? true})
|
||||
[:div.asset-picker-current
|
||||
{:class (when avatar-mode? "avatar-mode")}
|
||||
(image-asset-item current-asset {:on-chosen on-chosen
|
||||
:avatar-context avatar-context
|
||||
:selected? true})]])
|
||||
|
||||
;; Asset grid
|
||||
[:div.asset-picker-grid
|
||||
{:class (when avatar-mode? "avatar-mode")}
|
||||
(cond
|
||||
loading?
|
||||
[:div.flex.flex-col.items-center.justify-center.h-32.text-gray-08
|
||||
[:div.animate-spin (shui/tabler-icon "loader-2" {:size 32})]
|
||||
[:span.text-sm.mt-2 "Loading assets..."]]
|
||||
;; "Images" section
|
||||
[:div.pane-section
|
||||
(section-header {:title "Images"
|
||||
:count asset-count
|
||||
:expanded? true})
|
||||
|
||||
(seq filtered-assets)
|
||||
(for [asset filtered-assets]
|
||||
(rum/with-key
|
||||
(image-asset-item asset {:on-chosen on-chosen
|
||||
:avatar-context avatar-context})
|
||||
(str (:block/uuid asset))))
|
||||
;; Asset grid
|
||||
[:div.asset-picker-grid
|
||||
{:class (when avatar-mode? "avatar-mode")}
|
||||
(cond
|
||||
loading?
|
||||
[:div.flex.flex-col.items-center.justify-center.h-32.text-gray-08
|
||||
[:div.animate-spin (shui/tabler-icon "loader-2" {:size 32})]
|
||||
[:span.text-sm.mt-2 "Loading assets..."]]
|
||||
|
||||
:else
|
||||
[:div.flex.flex-col.items-center.justify-center.h-32.text-gray-08
|
||||
(shui/tabler-icon "photo-off" {:size 32})
|
||||
[:span.text-sm.mt-2 "No image assets found"]
|
||||
[:span.text-xs.mt-1 "Upload an image to get started"]])]
|
||||
(seq filtered-assets)
|
||||
(for [asset filtered-assets]
|
||||
(rum/with-key
|
||||
(image-asset-item asset {:on-chosen on-chosen
|
||||
:avatar-context avatar-context
|
||||
:selected? (= (str (:block/uuid asset)) current-asset-uuid)})
|
||||
(str (:block/uuid asset))))
|
||||
|
||||
:else
|
||||
[:div.flex.flex-col.items-center.justify-center.h-32.text-gray-08
|
||||
(shui/tabler-icon "photo-off" {:size 32})
|
||||
[:span.text-sm.mt-2 "No image assets found"]
|
||||
[:span.text-xs.mt-1 "Upload an image to get started"]])]]]
|
||||
|
||||
;; Action buttons (floating at bottom)
|
||||
[:div.asset-picker-actions
|
||||
@@ -1456,6 +1486,9 @@
|
||||
((:on-chosen opts) e icon-data)
|
||||
(reset! *view :icon-picker))
|
||||
:on-back #(reset! *view :icon-picker)
|
||||
:on-delete #(on-chosen nil)
|
||||
:del-btn? del-btn?
|
||||
:current-icon normalized-icon-value
|
||||
:avatar-context (when (= :avatar (:type normalized-icon-value))
|
||||
normalized-icon-value)})
|
||||
;; Level 1: Icon Picker view
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
}
|
||||
|
||||
.pane-section {
|
||||
@apply pl-2 overflow-y-auto h-full;
|
||||
@apply px-2 overflow-y-auto h-full;
|
||||
|
||||
color: var(--ls-color-icon-preset);
|
||||
|
||||
@@ -271,6 +271,16 @@
|
||||
min-height: 320px;
|
||||
max-height: 440px;
|
||||
background-color: var(--lx-gray-02);
|
||||
|
||||
/* Body content - 4px padding top/bottom */
|
||||
> .bd {
|
||||
@apply py-1;
|
||||
}
|
||||
|
||||
/* Sections - 8px padding on both sides (symmetric) */
|
||||
.pane-section {
|
||||
@apply px-2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Topbar wrapper for easier inspection */
|
||||
@@ -278,9 +288,9 @@
|
||||
@apply flex-shrink-0;
|
||||
}
|
||||
|
||||
/* Back button as link-style button */
|
||||
/* Back button row with delete button on right */
|
||||
.asset-picker-back {
|
||||
@apply flex-shrink-0;
|
||||
@apply flex-shrink-0 flex items-center justify-between;
|
||||
/* py-2 + content height matches icon-picker topbar */
|
||||
padding-top: 8px;
|
||||
padding-bottom: 7px;
|
||||
@@ -301,6 +311,12 @@
|
||||
color: var(--rx-gray-12);
|
||||
}
|
||||
}
|
||||
|
||||
/* Delete button - matching icon picker styling */
|
||||
.ui__button[data-action=del] {
|
||||
@apply !w-6 !h-6 overflow-hidden rounded-md opacity-60;
|
||||
@apply hover:text-red-rx-09 hover:opacity-90;
|
||||
}
|
||||
}
|
||||
|
||||
/* Search input matching icon picker */
|
||||
@@ -326,26 +342,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Section header matching icon picker */
|
||||
.asset-picker-section-header {
|
||||
@apply flex items-center justify-between px-4 py-1;
|
||||
@apply text-[10px] tracking-tight;
|
||||
@apply flex-shrink-0;
|
||||
color: var(--rx-gray-11);
|
||||
|
||||
.section-title {
|
||||
@apply flex items-center gap-0.5;
|
||||
}
|
||||
|
||||
.section-actions {
|
||||
@apply flex items-center gap-2;
|
||||
|
||||
.shortcut {
|
||||
@apply text-xs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Grid layout - 5 columns for larger, scannable images */
|
||||
.asset-picker-grid {
|
||||
@apply grid gap-2 p-3;
|
||||
@@ -381,7 +377,7 @@
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-fit: contain;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@@ -399,22 +395,45 @@
|
||||
|
||||
img {
|
||||
@apply rounded-full;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
> div {
|
||||
@apply rounded-full;
|
||||
}
|
||||
}
|
||||
|
||||
/* Selected state - blue outline */
|
||||
&.selected {
|
||||
border-color: var(--rx-blue-09);
|
||||
box-shadow: 0 0 0 1px var(--rx-blue-09);
|
||||
}
|
||||
}
|
||||
|
||||
/* Action buttons - floating at bottom */
|
||||
/* Current selection section - single item display */
|
||||
.asset-picker-current {
|
||||
@apply px-3 py-2;
|
||||
@apply flex-shrink-0;
|
||||
|
||||
.image-asset-item {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
padding-bottom: 0; /* Override aspect ratio hack for fixed size */
|
||||
}
|
||||
}
|
||||
|
||||
/* Action buttons - floating at bottom with fade effect */
|
||||
.asset-picker-actions {
|
||||
@apply flex items-center justify-center gap-2 px-3 py-2;
|
||||
@apply absolute bottom-2 left-1/2 -translate-x-1/2;
|
||||
@apply rounded-lg;
|
||||
background-color: var(--lx-gray-03);
|
||||
border: 1px solid var(--rx-gray-06);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
@apply flex items-center justify-end gap-2;
|
||||
@apply absolute bottom-0 left-0 right-0;
|
||||
@apply py-3 pr-3;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent 0%,
|
||||
color-mix(in srgb, var(--lx-gray-03) 30%, transparent) 30%,
|
||||
color-mix(in srgb, var(--lx-gray-03) 70%, transparent) 60%,
|
||||
var(--lx-gray-03) 100%
|
||||
);
|
||||
|
||||
.secondary-button {
|
||||
@apply flex items-center gap-1.5 px-2.5 py-1.5 rounded-md;
|
||||
|
||||
Reference in New Issue
Block a user