mirror of
https://github.com/logseq/logseq.git
synced 2026-05-21 19:24:17 +00:00
feat: row icons in tables + batch Set icon button
Adds an icon prefix to the Name cell of class-instance tables and a batch Set icon button to the row-selection action bar. Avatar/text initials are derived per row, so batch-applying an avatar to multiple rows yields each row's own initials while preserving picker colors. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,58 @@
|
||||
(ns frontend.components.selection
|
||||
"Block selection"
|
||||
(:require [frontend.db :as db]
|
||||
(:require [frontend.components.icon :as icon-component]
|
||||
[frontend.db :as db]
|
||||
[frontend.handler.db-based.property :as db-property-handler]
|
||||
[frontend.handler.editor :as editor-handler]
|
||||
[frontend.handler.property :as property-handler]
|
||||
[frontend.state :as state]
|
||||
[frontend.ui :as ui]
|
||||
[frontend.util :as util]
|
||||
[logseq.shui.ui :as shui]
|
||||
[rum.core :as rum]))
|
||||
|
||||
(defn- icon-data-for-block
|
||||
"Compute per-block icon-data. For avatar/text, replace the picker's
|
||||
preview-derived :value with initials derived from each block's own
|
||||
title — otherwise every batched row would show the first row's initials."
|
||||
[icon block]
|
||||
(when icon
|
||||
(let [block-title (:block/title block)]
|
||||
(cond
|
||||
(= :avatar (:type icon))
|
||||
(let [colors (select-keys (:data icon) [:backgroundColor :color])]
|
||||
{:type :avatar
|
||||
:data (cond-> colors
|
||||
block-title (assoc :value (icon-component/derive-avatar-initials block-title)))})
|
||||
|
||||
(= :text (:type icon))
|
||||
(let [colors (select-keys (:data icon) [:color])]
|
||||
{:type :text
|
||||
:data (cond-> colors
|
||||
block-title (assoc :value (icon-component/derive-initials block-title)))})
|
||||
|
||||
(= :image (:type icon))
|
||||
{:type :image :data (:data icon)}
|
||||
|
||||
:else (select-keys icon [:type :id :color])))))
|
||||
|
||||
(defn- batch-write-icon!
|
||||
[blocks icon]
|
||||
(if (or (nil? icon) (contains? #{:avatar :text} (:type icon)))
|
||||
;; Per-block writes (avatar/text need per-row initials; clear/nil applies uniformly)
|
||||
(doseq [block blocks]
|
||||
(if (nil? icon)
|
||||
(property-handler/remove-block-property! (:db/id block) :logseq.property/icon)
|
||||
(db-property-handler/set-block-property!
|
||||
(:db/id block)
|
||||
:logseq.property/icon
|
||||
(icon-data-for-block icon block))))
|
||||
;; Uniform value across blocks (icon, emoji, image): single batch transaction
|
||||
(property-handler/batch-set-block-property!
|
||||
(map :db/id blocks)
|
||||
:logseq.property/icon
|
||||
(icon-data-for-block icon (first blocks)))))
|
||||
|
||||
(rum/defc action-bar < rum/reactive
|
||||
[& {:keys [on-cut on-copy selected-blocks hide-dots? button-border? view-parent]
|
||||
:or {on-cut #(editor-handler/cut-selection-blocks true)}}]
|
||||
@@ -35,6 +80,31 @@
|
||||
:on-dialog-close #(state/pub-event! [:editor/hide-action-bar])}])))
|
||||
(ui/tooltip (ui/icon "hash" {:size 13}) "Set tag"
|
||||
{:trigger-props {:class "flex"}}))
|
||||
;; set icon (batch)
|
||||
(shui/button
|
||||
(assoc button-opts
|
||||
:on-pointer-down (fn [^js e]
|
||||
(util/stop e)
|
||||
(let [target (.-target e)
|
||||
first-title (some-> selected-blocks first :block/title)]
|
||||
(shui/popup-show!
|
||||
target
|
||||
(fn [{:keys [id]}]
|
||||
(icon-component/icon-search
|
||||
{:on-chosen (fn [_e icon-value keep-popup?]
|
||||
(batch-write-icon! selected-blocks icon-value)
|
||||
(when-not (true? keep-popup?)
|
||||
(shui/popup-hide! id)
|
||||
(state/pub-event! [:editor/hide-action-bar])))
|
||||
:icon-value nil
|
||||
:page-title first-title
|
||||
:del-btn? false}))
|
||||
{:align :start
|
||||
:id :ls-icon-picker
|
||||
:content-props {:class "ls-icon-picker"
|
||||
:onEscapeKeyDown #(.preventDefault %)}}))))
|
||||
(ui/tooltip (ui/icon "mood-smile" {:size 13}) "Set icon"
|
||||
{:trigger-props {:class "flex"}}))
|
||||
(shui/button
|
||||
(assoc button-opts
|
||||
:on-pointer-down (fn [e]
|
||||
|
||||
@@ -46,6 +46,14 @@
|
||||
.multi-values {
|
||||
@apply !flex-nowrap;
|
||||
}
|
||||
|
||||
.table-row-icon {
|
||||
@apply cursor-pointer rounded transition-colors duration-100;
|
||||
|
||||
&:hover {
|
||||
background: var(--rx-gray-04);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ls-table-header {
|
||||
|
||||
@@ -241,7 +241,7 @@
|
||||
|
||||
(rum/defc ^:large-vars/cleanup-todo block-title < rum/static
|
||||
"Used on table view"
|
||||
[block* {:keys [create-new-block width row property]}]
|
||||
[block* {:keys [create-new-block width row property property-ident]}]
|
||||
(let [*ref (hooks/use-ref nil)
|
||||
[opacity set-opacity!] (hooks/use-state 0)
|
||||
[focus-timeout set-focus-timeout!] (hooks/use-state nil)
|
||||
@@ -303,9 +303,45 @@
|
||||
(save-block-and-focus *ref set-focus-timeout! false))})
|
||||
(editor-handler/edit-block! block :max {:container-id :unknown-container})))))))}
|
||||
(if block
|
||||
[:div.flex.flex-row
|
||||
[:div.flex.flex-row.items-center.gap-2.min-w-0
|
||||
(when (and (= property-ident :block/title) (not many?))
|
||||
(when-let [icon-el (icon-component/get-node-icon-cp block* {:size 16 :color? true})]
|
||||
[:div.table-row-icon.flex-shrink-0.flex.items-center.justify-center
|
||||
{:style {:width 20 :height 20}
|
||||
:on-click (fn [^js e]
|
||||
(util/stop-propagation e)
|
||||
(let [own-icon (:logseq.property/icon block*)]
|
||||
(shui/popup-show!
|
||||
(.-currentTarget e)
|
||||
(fn [{:keys [id]}]
|
||||
(icon-component/icon-search
|
||||
{:on-chosen (fn [_e icon-value keep-popup?]
|
||||
(let [icon-data (when icon-value
|
||||
(cond
|
||||
(= :text (:type icon-value)) {:type :text :data (:data icon-value)}
|
||||
(= :avatar (:type icon-value)) {:type :avatar :data (:data icon-value)}
|
||||
(= :image (:type icon-value)) {:type :image :data (:data icon-value)}
|
||||
:else (select-keys icon-value [:type :id :color])))]
|
||||
(if icon-data
|
||||
(property-handler/set-block-property!
|
||||
(:db/id block*)
|
||||
:logseq.property/icon
|
||||
icon-data)
|
||||
(property-handler/remove-block-property!
|
||||
(:db/id block*)
|
||||
:logseq.property/icon)))
|
||||
(when-not (true? keep-popup?)
|
||||
(shui/popup-hide! id)))
|
||||
:icon-value own-icon
|
||||
:page-title (:block/title block*)
|
||||
:del-btn? (some? own-icon)}))
|
||||
{:align :start
|
||||
:id :ls-icon-picker
|
||||
:content-props {:class "ls-icon-picker"
|
||||
:onEscapeKeyDown #(.preventDefault %)}})))}
|
||||
icon-el]))
|
||||
(let [render (fn [block]
|
||||
[:div
|
||||
[:div.min-w-0
|
||||
(inline-title
|
||||
{:table? true
|
||||
:block/uuid (:block/uuid block)}
|
||||
|
||||
Reference in New Issue
Block a user