mirror of
https://github.com/logseq/logseq.git
synced 2026-05-28 14:39:48 +00:00
enhance: improve gallery view (#12698)
* enhance: improve gallery view * fix: tune gallery card sizing * fix: load gallery asset properties initially * chore: cleanup code * fix: gallery view should load block fully * fix: align gallery card media * fix: render asset class gallery cards * fix: align gallery images with header * fix: fit gallery images to card width * fix: polish gallery view actions * fix: improve gallery view behavior * fix: guard gallery asset loading * fix: hide asset placeholders while loading * fix: clean up gallery view loading * fix: improve gallery selection actions * fix: clean up gallery selection state * fix: improve grouped gallery view
This commit is contained in:
93
deps/db/src/logseq/db/common/view.cljs
vendored
93
deps/db/src/logseq/db/common/view.cljs
vendored
@@ -567,20 +567,37 @@
|
||||
(into [] (filter filter-pred) entities))
|
||||
entities)
|
||||
group-by-page? (= group-by-property-ident :block/page)
|
||||
readable-property-value-or-ent
|
||||
(fn readable-property-value-or-ent [ent]
|
||||
(let [pvalue (get ent group-by-property-ident)]
|
||||
(if (de/entity? pvalue)
|
||||
(if (match-property-value-as-entity? pvalue group-by-property)
|
||||
pvalue
|
||||
(db-property/property-value-content pvalue))
|
||||
pvalue)))
|
||||
group-values
|
||||
(fn group-values [ent]
|
||||
(let [pvalue (get ent group-by-property-ident)
|
||||
values (if (and (not (de/entity? pvalue))
|
||||
(coll? pvalue)
|
||||
(not (map? pvalue)))
|
||||
(seq pvalue)
|
||||
[pvalue])]
|
||||
(or
|
||||
(seq
|
||||
(map
|
||||
(fn [value]
|
||||
(if (de/entity? value)
|
||||
(if (match-property-value-as-entity? value group-by-property)
|
||||
value
|
||||
(db-property/property-value-content value))
|
||||
value))
|
||||
values))
|
||||
[nil])))
|
||||
result (if group-by-property-ident
|
||||
(let [groups-sort-by-property-ident (or (:db/ident (:logseq.property.view/sort-groups-by-property view))
|
||||
:block/journal-day)
|
||||
desc? (:logseq.property.view/sort-groups-desc? view)
|
||||
result (->> filtered-entities
|
||||
(group-by readable-property-value-or-ent)
|
||||
(reduce (fn [groups ent]
|
||||
(reduce
|
||||
(fn [groups value]
|
||||
(update groups value (fnil conj []) ent))
|
||||
groups
|
||||
(group-values ent)))
|
||||
{})
|
||||
(seq))
|
||||
keyfn (fn [groups-sort-by-property-ident]
|
||||
(fn [[by-value _]]
|
||||
@@ -589,8 +606,8 @@
|
||||
(let [v (get by-value groups-sort-by-property-ident)]
|
||||
(if (and (= groups-sort-by-property-ident :block/journal-day) (not desc?)
|
||||
(nil? (:block/journal-day by-value)))
|
||||
;; Use MAX_SAFE_INTEGER so non-journal pages (without :block/journal-day) are sorted
|
||||
;; after all journal pages when sorting by journal date.
|
||||
;; Use MAX_SAFE_INTEGER so non-journal pages (without :block/journal-day) are sorted
|
||||
;; after all journal pages when sorting by journal date.
|
||||
js/Number.MAX_SAFE_INTEGER
|
||||
v))
|
||||
group-by-closed-values?
|
||||
@@ -601,8 +618,8 @@
|
||||
by-value)))]
|
||||
(sort (common-util/by-sorting
|
||||
(cond->
|
||||
[{:get-value (keyfn groups-sort-by-property-ident)
|
||||
:asc? (not desc?)}]
|
||||
[{:get-value (keyfn groups-sort-by-property-ident)
|
||||
:asc? (not desc?)}]
|
||||
(not= groups-sort-by-property-ident :block/title)
|
||||
(conj {:get-value (keyfn :block/title)
|
||||
:asc? (not desc?)})))
|
||||
@@ -610,34 +627,34 @@
|
||||
(sort-entities db sorting filtered-entities))
|
||||
data' (if group-by-property-ident
|
||||
(map
|
||||
(fn [[by-value entities]]
|
||||
(let [by-value' (if (de/entity? by-value)
|
||||
(select-keys by-value [:db/id :db/ident :block/uuid :block/title :block/name :logseq.property/value :logseq.property/icon :block/tags])
|
||||
by-value)
|
||||
pages? (not (some :block/page entities))
|
||||
group (if (and list-view? (not pages?))
|
||||
(let [parent-groups (->> entities
|
||||
(group-by :block/parent)
|
||||
(sort-by (fn [[parent _]] (:block/order parent))))]
|
||||
(map
|
||||
(fn [[_parent blocks]]
|
||||
[(:block/uuid (first blocks))
|
||||
(map (fn [b]
|
||||
{:db/id (:db/id b)
|
||||
:block/parent (:block/uuid (:block/parent b))})
|
||||
(ldb/sort-by-order blocks))])
|
||||
parent-groups))
|
||||
(->> (sort-entities db sorting entities)
|
||||
(map :db/id)))]
|
||||
[by-value' group]))
|
||||
result)
|
||||
(fn [[by-value entities]]
|
||||
(let [by-value' (if (de/entity? by-value)
|
||||
(select-keys by-value [:db/id :db/ident :block/uuid :block/title :block/name :logseq.property/value :logseq.property/icon :block/tags])
|
||||
by-value)
|
||||
pages? (not (some :block/page entities))
|
||||
group (if (and list-view? (not pages?))
|
||||
(let [parent-groups (->> entities
|
||||
(group-by :block/parent)
|
||||
(sort-by (fn [[parent _]] (:block/order parent))))]
|
||||
(map
|
||||
(fn [[_parent blocks]]
|
||||
[(:block/uuid (first blocks))
|
||||
(map (fn [b]
|
||||
{:db/id (:db/id b)
|
||||
:block/parent (:block/uuid (:block/parent b))})
|
||||
(ldb/sort-by-order blocks))])
|
||||
parent-groups))
|
||||
(->> (sort-entities db sorting entities)
|
||||
(map :db/id)))]
|
||||
[by-value' group]))
|
||||
result)
|
||||
(map :db/id result))
|
||||
dedupe-data? (or (= feat-type :property-objects) query?)]
|
||||
(cond->
|
||||
{:count (count filtered-entities)
|
||||
:data (if dedupe-data?
|
||||
(distinct data')
|
||||
data')}
|
||||
{:count (count filtered-entities)
|
||||
:data (if dedupe-data?
|
||||
(distinct data')
|
||||
data')}
|
||||
(= feat-type :linked-references)
|
||||
(merge (select-keys entities-result [:ref-pages-count :ref-matched-children-ids]))
|
||||
query?
|
||||
|
||||
35
deps/db/src/logseq/db/frontend/property.cljs
vendored
35
deps/db/src/logseq/db/frontend/property.cljs
vendored
@@ -460,6 +460,41 @@
|
||||
:hide? true}
|
||||
:queryable? true}
|
||||
|
||||
:logseq.property.view/gallery-asset-property {:title "Gallery asset property"
|
||||
:schema
|
||||
{:type :property
|
||||
:hide? true
|
||||
:public? false}}
|
||||
|
||||
:logseq.property.view/gallery-display-properties {:title "Gallery display properties"
|
||||
:schema
|
||||
{:type :property
|
||||
:cardinality :many
|
||||
:hide? true
|
||||
:public? false}}
|
||||
|
||||
:logseq.property.view/gallery-card-size {:title "Gallery card size"
|
||||
:schema
|
||||
{:type :keyword
|
||||
:hide? true
|
||||
:public? false}
|
||||
:properties {:logseq.property/scalar-default-value :default}
|
||||
:rtc property-ignore-rtc}
|
||||
|
||||
:logseq.property.view/gallery-card-width {:title "Gallery card width"
|
||||
:schema
|
||||
{:type :raw-number
|
||||
:hide? true
|
||||
:public? false}
|
||||
:rtc property-ignore-rtc}
|
||||
|
||||
:logseq.property.view/gallery-card-height {:title "Gallery card height"
|
||||
:schema
|
||||
{:type :raw-number
|
||||
:hide? true
|
||||
:public? false}
|
||||
:rtc property-ignore-rtc}
|
||||
|
||||
:logseq.property.view/sort-groups-by-property {:title "View sort groups by"
|
||||
:schema
|
||||
{:type :property
|
||||
|
||||
2
deps/db/src/logseq/db/frontend/schema.cljs
vendored
2
deps/db/src/logseq/db/frontend/schema.cljs
vendored
@@ -30,7 +30,7 @@
|
||||
(map (juxt :major :minor)
|
||||
[(parse-schema-version x) (parse-schema-version y)])))
|
||||
|
||||
(def version (parse-schema-version "65.32"))
|
||||
(def version (parse-schema-version "65.33"))
|
||||
|
||||
(defn major-version
|
||||
"Return a number.
|
||||
|
||||
37
deps/db/test/logseq/db/common/view_test.cljs
vendored
37
deps/db/test/logseq/db/common/view_test.cljs
vendored
@@ -8,7 +8,7 @@
|
||||
[conn feature-type & {:keys [view-for-id]}]
|
||||
(let [tx (d/transact! conn [(cond-> {:db/id -100
|
||||
:block/title "Test view"
|
||||
:block/uuid #uuid "00000000-0000-0000-0000-000000000100"
|
||||
:block/uuid (random-uuid)
|
||||
:logseq.property.view/feature-type feature-type
|
||||
:logseq.property.view/type :logseq.property.view/type.table}
|
||||
view-for-id
|
||||
@@ -86,6 +86,41 @@
|
||||
(is (= 1 (:count result)))
|
||||
(is (= ["B"] titles))))
|
||||
|
||||
(deftest get-view-data-class-objects-groups-by-title-test
|
||||
(let [conn (db-test/create-conn-with-blocks
|
||||
{:classes {:Topic {:block/title "Topic"}}
|
||||
:pages-and-blocks
|
||||
[{:page {:block/title "A" :build/tags [:Topic]}}
|
||||
{:page {:block/title "B" :build/tags [:Topic]}}]})
|
||||
class-id (:db/id (d/entity @conn :user.class/Topic))
|
||||
view-id (create-view-id conn :class-objects :view-for-id class-id)
|
||||
_ (d/transact! conn [[:db/add view-id :logseq.property.view/group-by-property :block/title]])
|
||||
result (db-view/get-view-data @conn view-id {:view-feature-type :class-objects
|
||||
:view-for-id class-id})
|
||||
group-titles (map first (:data result))]
|
||||
(is (= ["A" "B"] group-titles))))
|
||||
|
||||
(deftest get-view-data-class-objects-groups-by-many-values-test
|
||||
(let [conn (db-test/create-conn-with-blocks
|
||||
{:classes {:Topic {:block/title "Topic"}
|
||||
:SciFi {:block/title "Sci-Fi"}
|
||||
:Drama {:block/title "Drama"}}
|
||||
:pages-and-blocks
|
||||
[{:page {:block/title "Movie A" :build/tags [:Topic :SciFi :Drama]}}
|
||||
{:page {:block/title "Movie B" :build/tags [:Topic :SciFi]}}]})
|
||||
class-id (:db/id (d/entity @conn :user.class/Topic))
|
||||
view-id (create-view-id conn :class-objects :view-for-id class-id)
|
||||
_ (d/transact! conn [[:db/add view-id :logseq.property.view/group-by-property :block/tags]])
|
||||
result (db-view/get-view-data @conn view-id {:view-feature-type :class-objects
|
||||
:view-for-id class-id})
|
||||
group->titles (into {}
|
||||
(map (fn [[group rows]]
|
||||
[(:block/title group)
|
||||
(set (map (fn [id] (:block/title (d/entity @conn id))) rows))]))
|
||||
(:data result))]
|
||||
(is (= #{"Movie A" "Movie B"} (get group->titles "Sci-Fi")))
|
||||
(is (= #{"Movie A"} (get group->titles "Drama")))))
|
||||
|
||||
(deftest get-view-data-linked-references-page-view-does-not-crash-on-missing-db-ident-test
|
||||
(let [conn (db-test/create-conn-with-blocks
|
||||
{:pages-and-blocks
|
||||
|
||||
@@ -226,13 +226,13 @@
|
||||
(defonce *resizing-image? (atom false))
|
||||
|
||||
(rum/defc ^:large-vars/cleanup-todo asset-container
|
||||
[asset-block src title metadata {:keys [breadcrumb? positioned? local? full-text]}]
|
||||
[asset-block src title metadata {:keys [breadcrumb? positioned? local? full-text gallery-view?]}]
|
||||
(let [asset-width (:logseq.property.asset/width asset-block)
|
||||
asset-height (:logseq.property.asset/height asset-block)
|
||||
asset-align (normalize-asset-align (:logseq.property.asset/align asset-block))]
|
||||
(hooks/use-effect!
|
||||
(fn []
|
||||
(when (:block/uuid asset-block)
|
||||
(when (and (seq src) (:block/uuid asset-block))
|
||||
(when-not (or asset-width asset-height)
|
||||
(measure-image!
|
||||
src
|
||||
@@ -244,9 +244,11 @@
|
||||
(fn []))
|
||||
[])
|
||||
(let [*el-ref (rum/use-ref nil)
|
||||
image-src (fs/asset-path-normalize src)
|
||||
src' (if (or (string/starts-with? src "/")
|
||||
(string/starts-with? src "~"))
|
||||
image-src (when (seq src)
|
||||
(fs/asset-path-normalize src))
|
||||
src' (if (and (seq src)
|
||||
(or (string/starts-with? src "/")
|
||||
(string/starts-with? src "~")))
|
||||
(str "file://" src)
|
||||
src)
|
||||
get-blockid #(some-> (rum/deref *el-ref) (.closest "[blockid]") (.getAttribute "blockid") (uuid))]
|
||||
@@ -260,10 +262,11 @@
|
||||
:ref *el-ref}
|
||||
[:img.rounded-sm.relative.fade-in.fade-in-faster
|
||||
(merge
|
||||
{:loading "lazy"
|
||||
:referrerPolicy "no-referrer"
|
||||
:src src'
|
||||
:title title}
|
||||
(cond-> {:loading "lazy"
|
||||
:referrerPolicy "no-referrer"
|
||||
:src src'}
|
||||
(not gallery-view?)
|
||||
(assoc :title title))
|
||||
metadata)]
|
||||
(when (and (not breadcrumb?)
|
||||
(not positioned?))
|
||||
@@ -412,7 +415,8 @@
|
||||
{:breadcrumb? breadcrumb?
|
||||
:positioned? positioned?
|
||||
:local? local?
|
||||
:full-text full-text})]
|
||||
:full-text full-text
|
||||
:gallery-view? (:gallery-view? config)})]
|
||||
(if (or (:disable-resize? config)
|
||||
(:table-view? config)
|
||||
(not resizable?))
|
||||
@@ -516,9 +520,8 @@
|
||||
(p/then (fn [url]
|
||||
(reset! src (common-util/safe-decode-uri-component url))))
|
||||
(p/catch #(js/console.log "Failed to load asset:" %))))
|
||||
(:image-placeholder config)
|
||||
(if (and (:image-placeholder config) (nil? @src))
|
||||
(:image-placeholder config)
|
||||
(if (nil? @src)
|
||||
nil
|
||||
(let [asset-block (:asset-block config)
|
||||
ext (block-asset/link-ext @src href asset-block)
|
||||
repo (state/get-current-repo)
|
||||
@@ -1120,6 +1123,7 @@
|
||||
[:div.asset-transfer-progress-bar
|
||||
[:span {:style {:width (str percent "%")}}]]])
|
||||
image? (contains? (common-config/img-formats) (keyword asset-type))
|
||||
gallery-image? (and (:gallery-view? config) image?)
|
||||
width (get-in block [:logseq.property.asset/resize-metadata :width])
|
||||
asset-width (:logseq.property.asset/width block)
|
||||
asset-height (:logseq.property.asset/height block)
|
||||
@@ -1142,15 +1146,15 @@
|
||||
href (or (:logseq.property.asset/external-url block)
|
||||
(path/path-join (str "../" common-config/local-assets-dir) file))
|
||||
content (cond
|
||||
file-ready?
|
||||
(asset-link (assoc config
|
||||
:asset-block block
|
||||
:image-placeholder img-placeholder)
|
||||
(or file-ready? gallery-image?)
|
||||
(asset-link (cond-> (assoc config :asset-block block)
|
||||
(not gallery-image?)
|
||||
(assoc :image-placeholder img-placeholder))
|
||||
(:block/title block)
|
||||
href
|
||||
img-metadata
|
||||
nil)
|
||||
image?
|
||||
(and image? (not gallery-image?) (false? file-exists?))
|
||||
img-placeholder)]
|
||||
(if progress-view
|
||||
[:div.asset-transfer-shell
|
||||
@@ -2267,7 +2271,7 @@
|
||||
(when (and (state/developer-mode?) (.-metaKey event))
|
||||
(js/console.debug "[block config]==" config)))}
|
||||
[:span {:class (if (or (and control-show? (or collapsed? collapsable?))
|
||||
(and collapsed? (or page-title? order-list? config/publishing? (util/mobile?))))
|
||||
(and collapsed? (or order-list? config/publishing? (util/mobile?))))
|
||||
"control-show cursor-pointer"
|
||||
"control-hide")}
|
||||
(ui/rotating-arrow collapsed?)]])
|
||||
@@ -4234,7 +4238,7 @@
|
||||
:on-mouse-leave (fn [_e]
|
||||
(block-mouse-leave *control-show? block-id doc-mode?))}
|
||||
|
||||
(when (and (not property?) (not (:table-block-title? config)))
|
||||
(when (and (not property?) (not (:table-block-title? config)) (not (:hide-block-control? config)))
|
||||
(let [edit? (or editing?
|
||||
(= uuid (:block/uuid (state/get-edit-block))))]
|
||||
(block-control (assoc config :hide-bullet? (:page-title? config))
|
||||
@@ -4382,7 +4386,7 @@
|
||||
|
||||
(defn- config-block-should-update?
|
||||
[old-state new-state]
|
||||
(let [config-compare-keys [:show-cloze? :hide-children? :own-order-list-type :own-order-list-index :original-block :edit? :hide-bullet? :ref-matched-children-ids]
|
||||
(let [config-compare-keys [:show-cloze? :hide-children? :own-order-list-type :own-order-list-index :original-block :edit? :hide-bullet? :hide-block-control? :ref-matched-children-ids]
|
||||
b1 (second (:rum/args old-state))
|
||||
b2 (second (:rum/args new-state))
|
||||
result (or
|
||||
|
||||
@@ -1582,9 +1582,76 @@ html.is-mac {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.block-content-wrapper svg, .view-actions svg {
|
||||
.block-content-wrapper svg,
|
||||
.view-actions svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.view-actions,
|
||||
.view-actions > *,
|
||||
.view-actions button,
|
||||
.view-action-type,
|
||||
.view-action-type .property-value-inner,
|
||||
.view-action-type .property-value-inner > .jtrigger,
|
||||
.view-action-type .select-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.view-actions button,
|
||||
.view-action-type,
|
||||
.view-action-type .property-value-inner,
|
||||
.view-action-type .property-value-inner > .jtrigger {
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.view-action-type .property-value-inner {
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
flex: 0 0 24px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.view-action-type .property-value-inner > .jtrigger {
|
||||
flex: 0 0 24px;
|
||||
}
|
||||
|
||||
.view-action-type .select-item {
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.view-action-type .ui__icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.view-action-type:hover .property-value-inner {
|
||||
background: var(--lx-gray-03, var(--ls-tertiary-background-color, var(--rx-gray-03)));
|
||||
}
|
||||
|
||||
.view-actions button {
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
.filters-row {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ls-view-filter-value-item {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.katex svg {
|
||||
@@ -1670,6 +1737,18 @@ html.is-mac {
|
||||
@apply relative min-h-full;
|
||||
}
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.ls-page-title-container .ls-block-right > .block-tags {
|
||||
opacity: 0;
|
||||
transition: opacity 150ms ease-in;
|
||||
}
|
||||
|
||||
.ls-page-title-container:hover .ls-block-right > .block-tags,
|
||||
.ls-page-title-container:focus-within .ls-block-right > .block-tags {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.ls-code-editor-wrap {
|
||||
@apply relative w-full overflow-hidden rounded;
|
||||
|
||||
|
||||
@@ -1,6 +1,63 @@
|
||||
.ls-gallery-action-bar-slot {
|
||||
@apply fixed inset-x-0 bottom-0 z-[100] flex justify-center px-4;
|
||||
background: linear-gradient(to top, var(--ls-primary-background-color) 0%, var(--ls-primary-background-color) 42%, transparent 100%);
|
||||
padding-bottom: calc(1rem + env(safe-area-inset-bottom));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ls-gallery-action-bar {
|
||||
@apply flex h-8 max-w-[calc(100vw-2rem)] flex-row items-center overflow-x-auto rounded-md border border-border bg-background;
|
||||
box-shadow: 0 8px 24px rgb(0 0 0 / 0.18), 0 2px 6px rgb(0 0 0 / 0.12);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.ls-gallery-action-select-all {
|
||||
@apply flex h-8 w-8 shrink-0 cursor-pointer items-center justify-center border-r border-border;
|
||||
}
|
||||
|
||||
.ls-gallery-action-bar .ls-table-actions > div:first-child {
|
||||
@apply px-2;
|
||||
}
|
||||
|
||||
.ls-cards {
|
||||
--ls-gallery-card-width: 220px;
|
||||
--ls-gallery-card-height: 320px;
|
||||
position: relative;
|
||||
|
||||
.ls-card-item {
|
||||
@apply dark:shadow-gray-700 shadow-md rounded-md p-4 h-[15rem] w-full overflow-auto;
|
||||
@apply w-full overflow-hidden rounded-md border border-transparent bg-transparent shadow-none transition-all;
|
||||
@apply hover:border-border hover:shadow-sm active:border-primary active:shadow-md dark:hover:border-gray-700 dark:hover:shadow-gray-800;
|
||||
height: var(--ls-gallery-card-height);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.ls-card-item.has-gallery-asset {
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ls-gallery-card-content {
|
||||
display: grid;
|
||||
grid-template-rows: minmax(0, 1fr) auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ls-card-item.has-gallery-asset .ls-gallery-card-content {
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.ls-card-item.has-gallery-asset .ls-gallery-card-title {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.ls-card-item.has-gallery-asset .ls-gallery-card-meta,
|
||||
.ls-card-item.has-gallery-asset .ls-gallery-card-property {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
> div[data-virtuoso-scroller] {
|
||||
@@ -8,10 +65,183 @@
|
||||
}
|
||||
|
||||
.virtuoso-grid-list {
|
||||
@apply flex flex-wrap gap-3;
|
||||
@apply flex w-full flex-wrap justify-between gap-y-8;
|
||||
column-gap: 1rem;
|
||||
}
|
||||
|
||||
.virtuoso-grid-item {
|
||||
@apply flex w-[290px];
|
||||
@apply flex;
|
||||
width: min(var(--ls-gallery-card-width), 100%);
|
||||
flex: 0 1 min(var(--ls-gallery-card-width), 100%);
|
||||
}
|
||||
|
||||
.ls-gallery-card-media {
|
||||
@apply relative flex items-start justify-start overflow-hidden text-muted-foreground;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.ls-gallery-card-media .asset-action-bar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ls-gallery-card-select {
|
||||
@apply absolute right-2 top-2 z-10 h-7 w-7 opacity-0 transition-opacity;
|
||||
}
|
||||
|
||||
.ls-gallery-card-select button[role="checkbox"] {
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
min-width: 18px;
|
||||
min-height: 18px;
|
||||
padding: 0 !important;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgb(255 255 255 / 0.7);
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 3px rgb(0 0 0 / 0.2);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.ls-gallery-card-select button[role="checkbox"] svg {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ls-gallery-card-select button[role="checkbox"][data-state="checked"] {
|
||||
color: white;
|
||||
background-color: rgb(2 132 199 / 0.9);
|
||||
}
|
||||
|
||||
.ls-gallery-card-select button[role="checkbox"]:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgb(255 255 255 / 0.8), 0 1px 3px rgb(0 0 0 / 0.25);
|
||||
}
|
||||
|
||||
.dark-theme .ls-gallery-card-select button[role="checkbox"] {
|
||||
background-color: rgb(15 23 42 / 0.65);
|
||||
box-shadow: 0 1px 3px rgb(0 0 0 / 0.45);
|
||||
}
|
||||
|
||||
.dark-theme .ls-gallery-card-select button[role="checkbox"][data-state="checked"] {
|
||||
background-color: rgb(14 165 233 / 0.78);
|
||||
}
|
||||
|
||||
.dark-theme .ls-gallery-card-select button[role="checkbox"]:focus-visible {
|
||||
box-shadow: 0 0 0 2px rgb(15 23 42 / 0.85), 0 0 0 3px rgb(125 211 252 / 0.75);
|
||||
}
|
||||
|
||||
.ls-card-item:hover .ls-gallery-card-select,
|
||||
.ls-card-item.is-selected .ls-gallery-card-select {
|
||||
@apply opacity-100;
|
||||
}
|
||||
|
||||
.ls-card-item.is-selected {
|
||||
@apply shadow-md;
|
||||
}
|
||||
|
||||
.ls-card-item.has-gallery-asset .ls-gallery-card-media {
|
||||
@apply items-end justify-center;
|
||||
}
|
||||
|
||||
.ls-gallery-card-media .asset-container {
|
||||
@apply flex h-full items-start justify-center;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.ls-card-item.has-gallery-asset .ls-gallery-card-media .asset-container {
|
||||
@apply items-end;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.ls-gallery-card-media img {
|
||||
@apply block max-h-full max-w-full object-contain;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
object-position: center top;
|
||||
transform: scale(1);
|
||||
transform-origin: center center;
|
||||
transition: transform 160ms ease;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.ls-gallery-card-media:hover img {
|
||||
transform: scale(1.04);
|
||||
}
|
||||
|
||||
.ls-gallery-card-media video,
|
||||
.ls-gallery-card-media audio {
|
||||
@apply max-h-full max-w-full;
|
||||
}
|
||||
|
||||
.ls-gallery-card-meta {
|
||||
@apply flex flex-col gap-1 px-1 py-1.5;
|
||||
box-sizing: border-box;
|
||||
width: 0;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.ls-card-item:not(.has-gallery-asset) .ls-gallery-card-meta {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ls-gallery-card-title {
|
||||
@apply min-w-0 truncate text-sm font-medium leading-snug text-foreground opacity-100;
|
||||
}
|
||||
|
||||
.ls-gallery-card-property {
|
||||
@apply min-w-0 truncate text-sm font-normal leading-snug text-foreground opacity-90;
|
||||
}
|
||||
|
||||
.ls-gallery-card-title * {
|
||||
@apply text-sm font-normal leading-snug text-foreground opacity-100;
|
||||
}
|
||||
|
||||
.ls-gallery-card-property * {
|
||||
@apply text-sm font-normal leading-snug text-foreground opacity-100;
|
||||
}
|
||||
|
||||
.ls-gallery-card-property .multi-values {
|
||||
@apply block min-w-0;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.ls-gallery-card-property .multi-values > * {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.ls-gallery-card-property .multi-values > .opacity-50 {
|
||||
@apply ml-0 opacity-100;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.virtuoso-grid-list {
|
||||
@apply gap-3;
|
||||
}
|
||||
|
||||
.virtuoso-grid-item {
|
||||
width: calc((100% - 0.75rem) / 2);
|
||||
flex-basis: calc((100% - 0.75rem) / 2);
|
||||
}
|
||||
|
||||
.ls-card-item {
|
||||
height: auto;
|
||||
min-height: 220px;
|
||||
}
|
||||
|
||||
.ls-gallery-card-media {
|
||||
height: clamp(140px, 48vw, 220px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
.virtuoso-grid-item {
|
||||
width: 100%;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1314,7 +1314,7 @@
|
||||
(inline-text-cp (str value)))]))
|
||||
|
||||
(rum/defc single-value-select
|
||||
[block property value select-opts {:keys [value-render] :as opts}]
|
||||
[block property value select-opts {:keys [value-render popup-focus-trigger? popup-auto-focus-trigger?] :as opts}]
|
||||
(let [*el (hooks/use-ref nil)
|
||||
editing? (:editing? opts)
|
||||
type (:logseq.property/type property)
|
||||
@@ -1330,10 +1330,13 @@
|
||||
trigger-id (str "trigger-" (:container-id opts) "-" (:db/id block) "-" (:db/id property))
|
||||
show-popup! (fn [target]
|
||||
(shui/popup-show! target (fn [] (popup-content target))
|
||||
{:align "start"
|
||||
:as-dropdown? true
|
||||
:auto-focus? true
|
||||
:trigger-id trigger-id}))]
|
||||
(cond->
|
||||
{:align "start"
|
||||
:as-dropdown? true
|
||||
:auto-focus? (not (false? popup-auto-focus-trigger?))
|
||||
:trigger-id trigger-id}
|
||||
(some? popup-focus-trigger?)
|
||||
(assoc :focus-trigger? popup-focus-trigger?))))]
|
||||
(if editing?
|
||||
(popup-content nil)
|
||||
(let [show! (fn [e]
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
[cljs-time.format :as tf]
|
||||
[clojure.set :as set]
|
||||
[clojure.string :as string]
|
||||
[datascript.core :as d]
|
||||
[datascript.impl.entity :as de]
|
||||
[dommy.core :as dom]
|
||||
[frontend.common.missionary :as c.m]
|
||||
@@ -132,8 +133,51 @@
|
||||
:class (str "flex transition-opacity "
|
||||
(if (or show? checked?) "opacity-100" "opacity-0"))})]))
|
||||
|
||||
(rum/defc gallery-card-checkbox < rum/static
|
||||
[{:keys [row-selected? row-toggle-selected! data state data-fns]} row]
|
||||
(let [id (str (:db/id row) "-gallery-checkbox")
|
||||
checked? (row-selected? row)
|
||||
{:keys [last-selected-idx row-selection]} state
|
||||
{:keys [set-last-selected-idx! set-row-selection!]} data-fns]
|
||||
[:label.ls-gallery-card-select.flex.items-center.justify-center.cursor-pointer
|
||||
{:html-for id
|
||||
:on-click util/stop-propagation}
|
||||
(shui/checkbox
|
||||
{:id id
|
||||
:checked checked?
|
||||
:on-click (fn [e]
|
||||
(when (and (.-shiftKey e) last-selected-idx)
|
||||
(util/stop e)
|
||||
(let [idx (.indexOf data (:db/id row))]
|
||||
(when (not= last-selected-idx idx)
|
||||
(let [new-ids (keep (fn [idx] (util/nth-safe data idx))
|
||||
(range (min last-selected-idx idx)
|
||||
(inc (max last-selected-idx idx))))]
|
||||
(when (seq new-ids)
|
||||
(set-row-selection! (update row-selection :selected-ids set/union (set new-ids)))))))))
|
||||
:on-checked-change (fn [v]
|
||||
(p/do!
|
||||
(when v
|
||||
(db-async/<get-block (state/get-current-repo) (:db/id row) {:skip-refresh? true
|
||||
:children? false}))
|
||||
(let [idx (.indexOf data (:db/id row))]
|
||||
(if v
|
||||
(set-last-selected-idx! idx)
|
||||
(when (= idx last-selected-idx)
|
||||
(set-last-selected-idx! nil))))
|
||||
(row-toggle-selected! row-selection row v)))
|
||||
:aria-label (t :view.table/select-row)
|
||||
:class "flex"})]))
|
||||
|
||||
(defonce *last-header-action-target (atom nil))
|
||||
|
||||
(defn- prevent-view-action-button-focus
|
||||
[^js e]
|
||||
(let [target (.-target e)]
|
||||
(when (and (some-> target (.closest "button, [tabindex]"))
|
||||
(not (some-> target (.closest "input, textarea, select, [contenteditable='true']"))))
|
||||
(.preventDefault e))))
|
||||
|
||||
(defn- header-dropdown-click-should-hide?
|
||||
[target]
|
||||
(let [menu-item (some-> target (.closest "[role='menuitem']"))
|
||||
@@ -476,6 +520,252 @@
|
||||
(defonce groups-sort-by-property-identity->name
|
||||
(set/map-invert groups-sort-by-name->property-identity))
|
||||
|
||||
(def ^:private groupable-property-types
|
||||
#{:checkbox :class :date :default :node :number :string :url})
|
||||
|
||||
(def ^:private groupable-many-property-types
|
||||
#{:class :default :node})
|
||||
|
||||
(defn group-by-column?
|
||||
[column]
|
||||
(when-let [id (:id column)]
|
||||
(when-not (= id :block/title)
|
||||
(when-let [property (db/entity id)]
|
||||
(and (contains? groupable-property-types (:logseq.property/type property))
|
||||
(or (not (db-property/many? property))
|
||||
(contains? groupable-many-property-types (:logseq.property/type property))))))))
|
||||
|
||||
(defn- set-view-property!
|
||||
[view-entity property-ident value]
|
||||
(property-handler/set-block-property! (:db/id view-entity) property-ident value))
|
||||
|
||||
(defn- property-ident->id
|
||||
[property-ident]
|
||||
(:db/id (db/entity property-ident)))
|
||||
|
||||
(defn- gallery-asset-columns
|
||||
[columns]
|
||||
(filter (fn [column]
|
||||
(when-let [property (db/entity (:id column))]
|
||||
(= :asset (:logseq.property/type property))))
|
||||
columns))
|
||||
|
||||
(def ^:private gallery-default-card-dimensions
|
||||
{:width 220
|
||||
:height 320})
|
||||
|
||||
(def ^:private gallery-compact-card-dimensions
|
||||
{:width 160
|
||||
:height 232})
|
||||
|
||||
(def ^:private gallery-min-card-dimension 100)
|
||||
|
||||
(def ^:private gallery-max-card-dimension 1024)
|
||||
|
||||
(defn- clamp-gallery-card-dimension
|
||||
[value]
|
||||
(-> value
|
||||
(max gallery-min-card-dimension)
|
||||
(min gallery-max-card-dimension)))
|
||||
|
||||
(defn- gallery-column-ident
|
||||
[column]
|
||||
(or (:id column)
|
||||
(:db/ident column)))
|
||||
|
||||
(defn- gallery-column-property
|
||||
[db column]
|
||||
(cond
|
||||
(de/entity? column) column
|
||||
(gallery-column-ident column) (d/entity db (gallery-column-ident column))))
|
||||
|
||||
(defn- gallery-asset-property-column?
|
||||
[db column]
|
||||
(= :asset (:logseq.property/type (gallery-column-property db column))))
|
||||
|
||||
(defn- gallery-asset-property-idents
|
||||
[db columns]
|
||||
(->> columns
|
||||
(filter #(gallery-asset-property-column? db %))
|
||||
(keep gallery-column-ident)
|
||||
vec))
|
||||
|
||||
(defn- gallery-asset-property-ident
|
||||
[db view columns]
|
||||
(let [configured-ident (:db/ident (:logseq.property.view/gallery-asset-property view))
|
||||
view-for (:logseq.property/view-for view)
|
||||
feature-type (:logseq.property.view/feature-type view)
|
||||
asset-tag? (= :logseq.class/Asset (:db/ident view-for))
|
||||
tag-view? (and (= :class-objects feature-type)
|
||||
(ldb/class? view-for))
|
||||
query-view? (= :query-result feature-type)]
|
||||
(cond
|
||||
asset-tag?
|
||||
:block/uuid
|
||||
|
||||
configured-ident
|
||||
configured-ident
|
||||
|
||||
(or tag-view? query-view?)
|
||||
(let [asset-idents (gallery-asset-property-idents db columns)]
|
||||
(when (= 1 (count asset-idents))
|
||||
(first asset-idents))))))
|
||||
|
||||
(defn- gallery-display-property-idents
|
||||
[view columns asset-property-ident]
|
||||
(let [configured-idents (set (keep :db/ident (:logseq.property.view/gallery-display-properties view)))
|
||||
display-idents (if (seq configured-idents)
|
||||
(->> columns
|
||||
(keep gallery-column-ident)
|
||||
(filter configured-idents)
|
||||
vec)
|
||||
[:block/title])]
|
||||
(->> display-idents
|
||||
(remove #{:select :id asset-property-ident})
|
||||
vec)))
|
||||
|
||||
(defn- gallery-card-dimensions
|
||||
[view]
|
||||
(case (:logseq.property.view/gallery-card-size view)
|
||||
:compact
|
||||
gallery-compact-card-dimensions
|
||||
|
||||
:custom
|
||||
(let [width (:logseq.property.view/gallery-card-width view)
|
||||
height (:logseq.property.view/gallery-card-height view)]
|
||||
(if (and (number? width) (number? height) (pos? width) (pos? height))
|
||||
{:width (clamp-gallery-card-dimension width)
|
||||
:height (clamp-gallery-card-dimension height)}
|
||||
gallery-default-card-dimensions))
|
||||
|
||||
gallery-default-card-dimensions))
|
||||
|
||||
(defn- set-gallery-display-properties!
|
||||
[view-entity property-idents]
|
||||
(set-view-property! view-entity
|
||||
:logseq.property.view/gallery-display-properties
|
||||
(vec (keep property-ident->id property-idents))))
|
||||
|
||||
(defn- gallery-display-properties-menu
|
||||
[view-entity columns]
|
||||
(let [asset-property-ident (gallery-asset-property-ident (db/get-db) view-entity columns)
|
||||
display-property-idents (set (gallery-display-property-idents view-entity columns asset-property-ident))
|
||||
property-columns (remove #(contains? #{:select :id asset-property-ident} (:id %)) columns)]
|
||||
(shui/dropdown-menu-sub
|
||||
(shui/dropdown-menu-sub-trigger
|
||||
(t :view.gallery/display-properties))
|
||||
(shui/dropdown-menu-sub-content
|
||||
(for [column property-columns]
|
||||
(shui/dropdown-menu-checkbox-item
|
||||
{:key (str "gallery-display-" (:id column))
|
||||
:checked (contains? display-property-idents (:id column))
|
||||
:onCheckedChange (fn [checked?]
|
||||
(let [new-idents (if checked?
|
||||
(conj display-property-idents (:id column))
|
||||
(disj display-property-idents (:id column)))]
|
||||
(set-gallery-display-properties! view-entity
|
||||
(filter new-idents (map :id property-columns)))))
|
||||
:onSelect (fn [e] (.preventDefault e))}
|
||||
(:name column)))))))
|
||||
|
||||
(defn- gallery-asset-property-menu
|
||||
[view-entity columns]
|
||||
(let [asset-columns (seq (gallery-asset-columns columns))]
|
||||
(when asset-columns
|
||||
(let [asset-property-ident (gallery-asset-property-ident (db/get-db) view-entity columns)]
|
||||
(shui/dropdown-menu-sub
|
||||
(shui/dropdown-menu-sub-trigger
|
||||
(t :view.gallery/asset-property))
|
||||
(shui/dropdown-menu-sub-content
|
||||
(for [column asset-columns]
|
||||
(shui/dropdown-menu-checkbox-item
|
||||
{:key (str "gallery-asset-" (:id column))
|
||||
:checked (= asset-property-ident (:id column))
|
||||
:onCheckedChange (fn [checked?]
|
||||
(when checked?
|
||||
(set-view-property! view-entity
|
||||
:logseq.property.view/gallery-asset-property
|
||||
(property-ident->id (:id column)))))
|
||||
:onSelect (fn [e] (.preventDefault e))}
|
||||
(:name column)))))))))
|
||||
|
||||
(defn- gallery-slider-value
|
||||
[value]
|
||||
(-> (js/Math.round value)
|
||||
(max gallery-min-card-dimension)
|
||||
(min gallery-max-card-dimension)))
|
||||
|
||||
(rum/defc gallery-card-size-slider
|
||||
[label value on-change on-commit]
|
||||
[:div.flex.flex-col.gap-2
|
||||
[:div.flex.flex-row.items-center.justify-between.gap-3.text-sm.leading-none
|
||||
[:span label]
|
||||
[:span.font-medium.tabular-nums (str value \p \x)]]
|
||||
(shui/slider
|
||||
{:class "relative flex w-full touch-none select-none items-center"
|
||||
:value #js [value]
|
||||
:min gallery-min-card-dimension
|
||||
:max gallery-max-card-dimension
|
||||
:step 1
|
||||
:on-value-change (fn [result]
|
||||
(on-change (gallery-slider-value (first result))))
|
||||
:on-value-commit (fn [result]
|
||||
(on-commit (gallery-slider-value (first result))))}
|
||||
(shui/slider-track
|
||||
{:class "relative h-2 w-full grow overflow-hidden rounded-full bg-secondary"}
|
||||
(shui/slider-range
|
||||
{:class "absolute h-full bg-primary"}))
|
||||
(shui/slider-thumb
|
||||
{:class "block h-4 w-4 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none"}))])
|
||||
|
||||
(rum/defc gallery-custom-card-size-inputs
|
||||
[view-entity dimensions set-size!]
|
||||
(let [[width set-width!] (hooks/use-state (:width dimensions))
|
||||
[height set-height!] (hooks/use-state (:height dimensions))
|
||||
save-dimensions! (fn [width' height']
|
||||
(p/do!
|
||||
(set-size! :custom)
|
||||
(set-view-property! view-entity :logseq.property.view/gallery-card-width width')
|
||||
(set-view-property! view-entity :logseq.property.view/gallery-card-height height')))
|
||||
stop-menu-input! (fn [e]
|
||||
(when-not (= "Escape" (util/ekey e))
|
||||
(util/stop-propagation e)))]
|
||||
[:div
|
||||
{:class "flex flex-col items-stretch gap-2 w-[320px] max-w-[calc(100vw-32px)] px-4 py-2"
|
||||
:on-click util/stop-propagation
|
||||
:on-key-down stop-menu-input!}
|
||||
[:div.w-full.text-sm.leading-8 (t :view.gallery/custom-size)]
|
||||
[:div.flex.flex-col.gap-4.w-full
|
||||
(gallery-card-size-slider
|
||||
(t :view.gallery/width)
|
||||
width
|
||||
set-width!
|
||||
#(save-dimensions! % height))
|
||||
(gallery-card-size-slider
|
||||
(t :view.gallery/height)
|
||||
height
|
||||
set-height!
|
||||
#(save-dimensions! width %))]]))
|
||||
|
||||
(defn- gallery-card-size-menu
|
||||
[view-entity]
|
||||
(let [size (:logseq.property.view/gallery-card-size view-entity)
|
||||
dimensions (gallery-card-dimensions view-entity)
|
||||
set-size! #(set-view-property! view-entity :logseq.property.view/gallery-card-size %)]
|
||||
(shui/dropdown-menu-sub
|
||||
(shui/dropdown-menu-sub-trigger
|
||||
(t :view.gallery/card-size))
|
||||
(shui/dropdown-menu-sub-content
|
||||
(for [[value label] [[:default (t :view.gallery/default-size)]
|
||||
[:compact (t :view.gallery/compact-size)]]]
|
||||
(shui/dropdown-menu-checkbox-item
|
||||
{:key (str "gallery-size-" (name value))
|
||||
:checked (= value (or size :default))
|
||||
:onCheckedChange #(when % (set-size! value))
|
||||
:onSelect (fn [e] (.preventDefault e))}
|
||||
label))
|
||||
(gallery-custom-card-size-inputs view-entity dimensions set-size!)))))
|
||||
|
||||
(rum/defc groups-sort
|
||||
[view-entity sort-by-value]
|
||||
(let [property-ident (or (:db/ident sort-by-value) :block/journal-day)]
|
||||
@@ -519,18 +809,15 @@
|
||||
[view-entity columns {:keys [column-visible? rows column-toggle-visibility]} {:keys [group-by-property-ident]}]
|
||||
(let [display-type (:db/ident (:logseq.property.view/type view-entity))
|
||||
table? (= display-type :logseq.property.view/type.table)
|
||||
gallery? (= display-type :logseq.property.view/type.gallery)
|
||||
group-by-columns (concat (when (or
|
||||
(contains? #{:linked-references :unlinked-references}
|
||||
(:logseq.property.view/feature-type view-entity))
|
||||
(:logseq.property/query view-entity))
|
||||
[{:id :block/page
|
||||
:name (t :view.table/page)}])
|
||||
(filter (fn [column]
|
||||
(when (:id column)
|
||||
(when-let [p (db/entity (:id column))]
|
||||
(and (not (db-property/many? p))
|
||||
(contains? #{:default :number :checkbox :url :node :date}
|
||||
(:logseq.property/type p)))))) columns))
|
||||
(filter (fn [column]
|
||||
(group-by-column? column)) columns))
|
||||
group-by-page? (some #{:block/page} (map :id group-by-columns))]
|
||||
(shui/dropdown-menu
|
||||
(shui/dropdown-menu-trigger
|
||||
@@ -541,7 +828,8 @@
|
||||
:size :sm}
|
||||
(ui/icon "dots" {:size 15})))
|
||||
(shui/dropdown-menu-content
|
||||
{:align "end"}
|
||||
{:align "end"
|
||||
:onCloseAutoFocus #(.preventDefault %)}
|
||||
(shui/dropdown-menu-group
|
||||
(when table?
|
||||
(shui/dropdown-menu-sub
|
||||
@@ -557,6 +845,12 @@
|
||||
:onCheckedChange #(column-toggle-visibility column %)
|
||||
:onSelect (fn [e] (.preventDefault e))}
|
||||
(:name column))))))
|
||||
(when gallery?
|
||||
(gallery-display-properties-menu view-entity columns))
|
||||
(when gallery?
|
||||
(gallery-asset-property-menu view-entity columns))
|
||||
(when gallery?
|
||||
(gallery-card-size-menu view-entity))
|
||||
(when (seq group-by-columns)
|
||||
(shui/dropdown-menu-sub
|
||||
(shui/dropdown-menu-sub-trigger
|
||||
@@ -1205,7 +1499,8 @@
|
||||
(fn []
|
||||
(filter-property view-entity columns table opts))
|
||||
{:align :end
|
||||
:auto-focus? true}))}
|
||||
:focus-trigger? false
|
||||
:content-props {:onCloseAutoFocus #(.preventDefault %)}}))}
|
||||
(ui/icon "filter")))
|
||||
|
||||
(defn operator->text
|
||||
@@ -1342,7 +1637,7 @@
|
||||
false
|
||||
true)]
|
||||
(shui/button
|
||||
{:class "!px-2 rounded-none border-r"
|
||||
{:class "!px-2 rounded-none border-r min-w-0 max-w-full overflow-hidden"
|
||||
:variant "ghost"
|
||||
:size :sm
|
||||
:on-click (fn [e]
|
||||
@@ -1423,25 +1718,26 @@
|
||||
(keep #(db/entity [:block/uuid %]) value)
|
||||
:else
|
||||
value)]
|
||||
[:div.flex.flex-row.items-center.gap-1.text-xs
|
||||
(cond
|
||||
(de/entity? value)
|
||||
[:div (get-property-value-content value)]
|
||||
[:div.ls-view-filter-value.flex.flex-row.items-center.gap-1.text-xs.min-w-0.max-w-full.overflow-hidden
|
||||
(cond
|
||||
(de/entity? value)
|
||||
[:div.ls-view-filter-value-item (get-property-value-content value)]
|
||||
|
||||
(string? value)
|
||||
[:div value]
|
||||
(string? value)
|
||||
[:div.ls-view-filter-value-item value]
|
||||
|
||||
(boolean? value)
|
||||
[:div (str value)]
|
||||
(boolean? value)
|
||||
[:div.ls-view-filter-value-item (str value)]
|
||||
|
||||
(= value :empty)
|
||||
[:div (t :view.filter/empty)]
|
||||
(= value :empty)
|
||||
[:div.ls-view-filter-value-item (t :view.filter/empty)]
|
||||
|
||||
(seq value)
|
||||
(->> (map (fn [v] [:div (get-property-value-content v)]) value)
|
||||
(interpose [:div (t :view.filter/or)]))
|
||||
:else
|
||||
(t :view/all))])))))
|
||||
(seq value)
|
||||
(->> (map (fn [v] [:span (get-property-value-content v)]) value)
|
||||
(interpose [:span.flex-none ", "])
|
||||
(into [:div.ls-view-filter-value-item]))
|
||||
:else
|
||||
(t :view/all))])))))
|
||||
|
||||
(rum/defc filter-value < rum/static
|
||||
[view-entity table property operator value filters set-filters! idx opts]
|
||||
@@ -1477,8 +1773,8 @@
|
||||
(let [filters (get-in table [:state :filters])
|
||||
{:keys [set-filters!]} data-fns]
|
||||
(when (seq (:filters filters))
|
||||
[:div.filters-row.flex.flex-row.items-center.gap-4.justify-between.flex-wrap.py-2
|
||||
[:div.flex.flex-row.items-center.gap-2
|
||||
[:div.filters-row.flex.flex-row.items-center.gap-4.justify-between.flex-wrap.py-2.min-w-0.max-w-full
|
||||
[:div.flex.flex-row.items-center.gap-2.flex-wrap.min-w-0.max-w-full
|
||||
(map-indexed
|
||||
(fn [idx filter']
|
||||
(let [[property-ident operator value] filter'
|
||||
@@ -1489,7 +1785,7 @@
|
||||
(some (fn [column] (when (= (:id column) property-ident)
|
||||
{:db/ident (:id column)
|
||||
:block/title (:name column)})) columns)))]
|
||||
[:div.flex.flex-row.items-center.border.rounded
|
||||
[:div.flex.flex-row.items-center.border.rounded.min-w-0.max-w-full
|
||||
(shui/button
|
||||
{:class "!px-2 rounded-none border-r"
|
||||
:variant "ghost"
|
||||
@@ -1600,7 +1896,7 @@
|
||||
(set-sized-columns! sized-columns)))})
|
||||
|
||||
(rum/defc lazy-item
|
||||
[data idx {:keys [properties list-view? scrolling?]} item-render]
|
||||
[data idx {:keys [properties list-view? gallery-view? scrolling?]} item-render]
|
||||
(let [item (util/nth-safe data idx)
|
||||
db-id (cond (map? item) (:db/id item)
|
||||
(number? item) item
|
||||
@@ -1610,7 +1906,8 @@
|
||||
(when (= :full (:block.temp/load-status e))
|
||||
e)))
|
||||
[item set-item!] (hooks/use-state entity)
|
||||
opts (if list-view?
|
||||
list-or-gallery? (or list-view? gallery-view?)
|
||||
opts (if list-or-gallery?
|
||||
{:skip-refresh? true
|
||||
:children? false}
|
||||
{:children? false
|
||||
@@ -1622,7 +1919,7 @@
|
||||
(m/sp
|
||||
(when (and db-id (not item) (not scrolling?))
|
||||
(let [block (c.m/<? (db-async/<get-block (state/get-current-repo) db-id opts))
|
||||
block' (if list-view? (db/entity db-id) block)]
|
||||
block' (if list-or-gallery? (db/entity db-id) block)]
|
||||
(set-item! block')))))
|
||||
[db-id scrolling?])
|
||||
(let [item' (cond (map? item) item (number? item) {:db/id item})]
|
||||
@@ -1717,33 +2014,220 @@
|
||||
(lazy-item-render rows idx)
|
||||
(str "partition-" idx)))))))
|
||||
|
||||
(rum/defc gallery-property-value
|
||||
[block property-ident]
|
||||
(if (= :block/title property-ident)
|
||||
[:div.ls-gallery-card-title
|
||||
(some->> (:block/title block)
|
||||
string/trim
|
||||
string/split-lines
|
||||
first)]
|
||||
(when-let [property (db/entity property-ident)]
|
||||
[:div.ls-gallery-card-property
|
||||
(pv/property-value block property {:view? true
|
||||
:gallery-view? true})])))
|
||||
|
||||
(defn gallery-card-asset-block
|
||||
[block asset-property-ident]
|
||||
(let [asset-value (when (and block asset-property-ident (not= :block/uuid asset-property-ident))
|
||||
(get block asset-property-ident))
|
||||
->entity (fn [value]
|
||||
(cond
|
||||
(de/entity? value) value
|
||||
(number? value) (db/entity value)
|
||||
(uuid? value) (db/entity [:block/uuid value])
|
||||
:else value))]
|
||||
(cond
|
||||
(= :block/uuid asset-property-ident)
|
||||
block
|
||||
|
||||
(set? asset-value)
|
||||
(some ->entity asset-value)
|
||||
|
||||
(sequential? asset-value)
|
||||
(some ->entity asset-value)
|
||||
|
||||
:else
|
||||
(->entity asset-value))))
|
||||
|
||||
(rum/defc gallery-card-item
|
||||
[view-entity block config]
|
||||
[:div.ls-card-item.content
|
||||
{:key (str "view-card-" (:db/id view-entity) "-" (:db/id block))}
|
||||
[:div.-ml-4
|
||||
(block-container (assoc config
|
||||
:id (str (:block/uuid block))
|
||||
:gallery-view? true
|
||||
:view? true)
|
||||
block)]])
|
||||
[table view-entity block config {:keys [asset-property-ident display-property-idents]}]
|
||||
(let [asset-block (gallery-card-asset-block block asset-property-ident)
|
||||
asset-cp (state/get-component :block/asset-cp)
|
||||
render-asset? (and asset-block (fn? asset-cp))
|
||||
selected? ((:row-selected? table) block)]
|
||||
[:div.ls-card-item.content
|
||||
{:key (str "view-card-" (:db/id view-entity) "-" (:db/id block))
|
||||
:data-state (when selected? "selected")
|
||||
:class (str (when render-asset? "has-gallery-asset")
|
||||
(when selected? " is-selected"))
|
||||
:on-click (fn [e]
|
||||
(when-not (some-> (.-target e) (.closest (str "button, a, input, textarea, select, [role='menuitem'], "
|
||||
".ls-gallery-card-media, .ls-gallery-card-property")))
|
||||
(route-handler/redirect-to-page! (:block/uuid block))))}
|
||||
[:div.ls-gallery-card-content
|
||||
[:div.ls-gallery-card-media
|
||||
(gallery-card-checkbox table block)
|
||||
(when render-asset?
|
||||
(asset-cp (assoc config :disable-resize? true :gallery-view? true) asset-block))]
|
||||
[:div.ls-gallery-card-meta
|
||||
(for [property-ident display-property-idents
|
||||
:let [property-value (gallery-property-value block property-ident)]
|
||||
:when property-value]
|
||||
(rum/with-key
|
||||
property-value
|
||||
(str "gallery-property-" (:db/id block) "-" property-ident)))]]]))
|
||||
|
||||
(defn gallery-lazy-item-opts
|
||||
[option]
|
||||
(select-keys option [:properties]))
|
||||
|
||||
(defn view-row-ids
|
||||
[rows]
|
||||
(mapcat
|
||||
(fn [row]
|
||||
(cond
|
||||
(number? row)
|
||||
[row]
|
||||
|
||||
(map? row)
|
||||
(when-let [id (:db/id row)]
|
||||
[id])
|
||||
|
||||
(and (vector? row) (= 2 (count row)))
|
||||
(view-row-ids (second row))
|
||||
|
||||
:else
|
||||
[]))
|
||||
rows))
|
||||
|
||||
(defn grouped-gallery-row-ids
|
||||
[groups]
|
||||
(vec (distinct (view-row-ids groups))))
|
||||
|
||||
(defn group-readable-property-value
|
||||
[value]
|
||||
(cond
|
||||
(and (map? value) (or (:block/title value) (:logseq.property/value value)))
|
||||
(db-property/property-value-content value)
|
||||
|
||||
(= (:db/ident value) :logseq.property/empty-placeholder)
|
||||
(t :ui/empty)
|
||||
|
||||
:else
|
||||
(str value)))
|
||||
|
||||
(rum/defc gallery-action-bar
|
||||
[table option view-parent view-feature-type selected-rows]
|
||||
(when (seq selected-rows)
|
||||
(let [checkbox-id (str (:db/id (:view-entity table)) "-gallery-select-all")
|
||||
checked? (or (:selected-all? table)
|
||||
(and (:selected-some? table) "indeterminate"))]
|
||||
[:div.ls-gallery-action-bar-slot
|
||||
[:div.ls-gallery-action-bar
|
||||
[:label.ls-gallery-action-select-all
|
||||
{:html-for checkbox-id
|
||||
:title (t :view.table/select-all)}
|
||||
(shui/checkbox
|
||||
{:id checkbox-id
|
||||
:checked checked?
|
||||
:on-checked-change (fn [value]
|
||||
(p/do
|
||||
(when value
|
||||
(db-async/<get-blocks (state/get-current-repo) (:rows table) {}))
|
||||
((:toggle-selected-all! table) table value)))
|
||||
:aria-label (t :view.table/select-all)
|
||||
:class "flex"})]
|
||||
(action-bar table selected-rows
|
||||
(assoc option
|
||||
:on-delete-rows (fn [table selected-ids]
|
||||
(on-delete-rows view-parent view-feature-type table selected-ids))))]])))
|
||||
|
||||
(rum/defcs gallery-view < rum/static mixins/container-id
|
||||
[state {:keys [config]} table view-entity blocks *scroller-ref]
|
||||
(let [config' (assoc config :container-id (:container-id state))]
|
||||
[state {:keys [config view-parent view-feature-type] :as option} table view-entity blocks row-selection *scroller-ref]
|
||||
(let [config' (assoc config :container-id (:container-id state))
|
||||
columns (:columns table)
|
||||
dimensions (gallery-card-dimensions view-entity)
|
||||
asset-property-ident (gallery-asset-property-ident (db/get-db) view-entity columns)
|
||||
display-property-idents (gallery-display-property-idents view-entity columns asset-property-ident)
|
||||
selected-rows (shui/table-get-selection-rows row-selection (:rows table))
|
||||
render-card (fn [idx]
|
||||
(lazy-item blocks idx
|
||||
(assoc (gallery-lazy-item-opts option)
|
||||
:gallery-view? true)
|
||||
(fn [block]
|
||||
(gallery-card-item table view-entity block config'
|
||||
{:asset-property-ident asset-property-ident
|
||||
:display-property-idents display-property-idents}))))]
|
||||
[:div.ls-cards
|
||||
{:style {"--ls-gallery-card-width" (str (:width dimensions) "px")
|
||||
"--ls-gallery-card-height" (str (:height dimensions) "px")}}
|
||||
(when (seq blocks)
|
||||
(ui/virtualized-grid
|
||||
{:ref #(reset! *scroller-ref %)
|
||||
:total-count (count blocks)
|
||||
:custom-scroll-parent (get-scroll-parent config)
|
||||
:skipAnimationFrameInResizeObserver true
|
||||
:compute-item-key (fn [idx]
|
||||
(str (:db/id view-entity) "-card-" idx))
|
||||
:item-content (fn [idx]
|
||||
(lazy-item (:data table) idx {}
|
||||
(fn [block]
|
||||
(gallery-card-item view-entity block config'))))}))]))
|
||||
(if (:disable-virtualized? option)
|
||||
[:div.virtuoso-grid-list
|
||||
(for [idx (range (count blocks))]
|
||||
[:div.virtuoso-grid-item
|
||||
{:key (str (:db/id view-entity) "-card-" (util/nth-safe blocks idx))}
|
||||
(render-card idx)])]
|
||||
(ui/virtualized-grid
|
||||
{:ref #(reset! *scroller-ref %)
|
||||
:total-count (count blocks)
|
||||
:custom-scroll-parent (get-scroll-parent config)
|
||||
:skipAnimationFrameInResizeObserver true
|
||||
:compute-item-key (fn [idx]
|
||||
(str (:db/id view-entity) "-card-" (util/nth-safe blocks idx)))
|
||||
:item-content render-card})))
|
||||
(when-not (:hide-action-bar? option)
|
||||
(gallery-action-bar table option view-parent view-feature-type selected-rows))]))
|
||||
|
||||
(rum/defc gallery-group
|
||||
[view-entity option row-selection *scroller-ref groups idx table-map group-by-page? group-by-property]
|
||||
(let [[value group] (nth groups idx)
|
||||
table' (shui/table-option (assoc table-map :data group))
|
||||
title (cond
|
||||
(and group-by-page? (nil? value))
|
||||
[:div.text-muted-foreground.text-sm
|
||||
(t :view.table/pages)]
|
||||
|
||||
(some? value)
|
||||
(group-readable-property-value value)
|
||||
|
||||
:else
|
||||
(t :view.table/no-group-value (:block/title group-by-property)))]
|
||||
[:div.ls-gallery-group
|
||||
[:div.my-2 title]
|
||||
(gallery-view (assoc option
|
||||
:disable-virtualized? true
|
||||
:hide-action-bar? true)
|
||||
table'
|
||||
view-entity
|
||||
group
|
||||
row-selection
|
||||
*scroller-ref)]))
|
||||
|
||||
(rum/defc grouped-gallery-view < rum/static
|
||||
[table-map table option view-entity groups row-selection group-by-property group-by-property-ident *scroller-ref]
|
||||
(let [gallery-rows (grouped-gallery-row-ids groups)
|
||||
gallery-action-table (shui/table-option
|
||||
(assoc table-map
|
||||
:data gallery-rows
|
||||
:full-data (:full-data table)))
|
||||
selected-rows (shui/table-get-selection-rows row-selection (:rows gallery-action-table))
|
||||
group-by-page? (= :block/page group-by-property-ident)]
|
||||
[:div.flex.flex-col.border-t.pt-2.gap-2
|
||||
(virtualized-list
|
||||
{:class "group-gallery-view"
|
||||
:custom-scroll-parent (util/app-scroll-container-node)
|
||||
:increase-viewport-by {:top 300 :bottom 300}
|
||||
:compute-item-key (fn [idx]
|
||||
(str "gallery-group-" (:db/id view-entity) "-" idx))
|
||||
:skipAnimationFrameInResizeObserver true
|
||||
:total-count (count groups)
|
||||
:item-content
|
||||
(fn [idx]
|
||||
(gallery-group view-entity option row-selection *scroller-ref groups idx table-map group-by-page? group-by-property))}
|
||||
false)
|
||||
(gallery-action-bar gallery-action-table option (:view-parent option) (:view-feature-type option) selected-rows)]))
|
||||
|
||||
(defn- run-effects!
|
||||
[option {:keys [data]} *scroller-ref gallery? set-ready?]
|
||||
@@ -1833,7 +2317,9 @@
|
||||
(shui/popup-show! (.-target e)
|
||||
(fn [] (view-sorting-config table sorting columns))
|
||||
{:align :end
|
||||
:dropdown-menu? true}))}
|
||||
:dropdown-menu? true
|
||||
:focus-trigger? false
|
||||
:content-props {:onCloseAutoFocus #(.preventDefault %)}}))}
|
||||
(ui/icon "arrows-up-down")))
|
||||
|
||||
(rum/defc view-cp
|
||||
@@ -1848,7 +2334,7 @@
|
||||
(list-view option view-entity table *scroller-ref)
|
||||
|
||||
:logseq.property.view/type.gallery
|
||||
(gallery-view option table view-entity (:rows table) *scroller-ref)
|
||||
(gallery-view option table view-entity (:rows table) row-selection *scroller-ref)
|
||||
|
||||
(table-view table option row-selection *scroller-ref))]))
|
||||
|
||||
@@ -1948,21 +2434,25 @@
|
||||
(t :view/rename))
|
||||
(shui/dropdown-menu-sub-content
|
||||
(when-let [block-container-cp (state/get-component :block/container)]
|
||||
(block-container-cp {:display-title (display-view-title view)} view))))
|
||||
(shui/dropdown-menu-item
|
||||
{:key "Delete"
|
||||
:on-click (fn []
|
||||
(p/do!
|
||||
(editor-handler/delete-block-aux! view)
|
||||
(let [views' (remove (fn [v] (= (:db/id v) (:db/id view))) views)]
|
||||
(set-views! views')
|
||||
(set-view-entity! (first views'))
|
||||
(shui/popup-hide!))))}
|
||||
(t :ui/delete))])
|
||||
(block-container-cp {:display-title (display-view-title view)
|
||||
:hide-block-control? true} view))))
|
||||
(when (> (count views) 1)
|
||||
(shui/dropdown-menu-item
|
||||
{:key "Delete"
|
||||
:on-click (fn []
|
||||
(p/do!
|
||||
(editor-handler/delete-block-aux! view)
|
||||
(let [views' (remove (fn [v] (= (:db/id v) (:db/id view))) views)]
|
||||
(set-views! views')
|
||||
(set-view-entity! (first views'))
|
||||
(shui/popup-hide!))))}
|
||||
(t :ui/delete)))])
|
||||
{:as-dropdown? true
|
||||
:dropdown-menu? true
|
||||
:align "start"
|
||||
:content-props {:onClick shui/popup-hide!}})
|
||||
:focus-trigger? false
|
||||
:content-props {:onClick shui/popup-hide!
|
||||
:onCloseAutoFocus #(.preventDefault %)}})
|
||||
(do
|
||||
(set-view-entity! view)
|
||||
(set-data! nil))))}
|
||||
@@ -2018,7 +2508,8 @@
|
||||
:opacity opacity
|
||||
:references? references?)))]
|
||||
[:div.view-actions.flex.items-center.gap-1.transition-opacity.ease-in.duration-300
|
||||
{:class opacity}
|
||||
{:class opacity
|
||||
:on-mouse-down prevent-view-action-button-focus}
|
||||
|
||||
(when (seq additional-actions)
|
||||
[:<> (for [action additional-actions]
|
||||
@@ -2036,29 +2527,34 @@
|
||||
:set-input! set-input!})]
|
||||
|
||||
[:div.view-action-type.text-muted-foreground.text-sm
|
||||
(pv/property-value view-entity (db/entity :logseq.property.view/type) {:icon? true})]
|
||||
(pv/property-value view-entity (db/entity :logseq.property.view/type) {:icon? true
|
||||
:popup-focus-trigger? false
|
||||
:popup-auto-focus-trigger? false})]
|
||||
|
||||
(more-actions view-entity columns table option)
|
||||
|
||||
(when add-new-object! (new-record-button table view-entity))]]))
|
||||
|
||||
(rum/defc group-item
|
||||
[view-entity table' group group-by-property value option view-opts {:keys [list-view? group-by-page? readable-property-value]}]
|
||||
[view-entity table' group group-by-property value option view-opts {:keys [list-view? gallery? group-by-page? readable-property-value]}]
|
||||
(let [title [:div
|
||||
{:class (when-not list-view? "my-2")}
|
||||
(cond
|
||||
group-by-page?
|
||||
(if value
|
||||
(let [c (state/get-component :block/page-cp)]
|
||||
(c {:disable-preview? true} value))
|
||||
[:div.text-muted-foreground.text-sm
|
||||
(t :view.table/pages)])
|
||||
group-by-page?
|
||||
(if value
|
||||
(let [c (state/get-component :block/page-cp)]
|
||||
(if (fn? c)
|
||||
(c {:disable-preview? true} value)
|
||||
(readable-property-value value)))
|
||||
[:div.text-muted-foreground.text-sm
|
||||
(t :view.table/pages)])
|
||||
|
||||
(some? value)
|
||||
(let [icon (pu/get-block-property-value value :logseq.property/icon)]
|
||||
[:div.flex.flex-row.gap-1.items-center
|
||||
(when icon (icon-component/icon icon {:color? true}))
|
||||
(readable-property-value value)])
|
||||
(some? value)
|
||||
(let [icon (when (map? value)
|
||||
(pu/get-block-property-value value :logseq.property/icon))]
|
||||
[:div.flex.flex-row.gap-1.items-center
|
||||
(when icon (icon-component/icon icon {:color? true}))
|
||||
(readable-property-value value)])
|
||||
|
||||
:else
|
||||
(t :view.table/no-group-value (:block/title group-by-property)))]
|
||||
@@ -2067,7 +2563,8 @@
|
||||
(assoc table' :rows group)
|
||||
(assoc option
|
||||
;; disabled virtualization for nested view
|
||||
:disable-virtualized? true)
|
||||
:disable-virtualized? true
|
||||
:hide-action-bar? gallery?)
|
||||
view-opts)]
|
||||
(if (and list-view? (not (util/mobile?)))
|
||||
[:div.-ml-2 render]
|
||||
@@ -2168,40 +2665,35 @@
|
||||
:add-new-object! add-new-object!}]
|
||||
(if (and group-by-property-ident (not (number? (first (:rows table)))))
|
||||
(when (and ready? (seq (:rows table)))
|
||||
[:div.flex.flex-col.border-t.pt-2.gap-2
|
||||
(virtualized-list
|
||||
{:class (when list-view? "group-list-view")
|
||||
:custom-scroll-parent (util/app-scroll-container-node (rum/deref *view-ref))
|
||||
:increase-viewport-by {:top 300 :bottom 300}
|
||||
:compute-item-key (fn [idx]
|
||||
(str "table-group" idx))
|
||||
:skipAnimationFrameInResizeObserver true
|
||||
:total-count (count (:rows table))
|
||||
:item-content (fn [idx]
|
||||
(let [[value group] (nth (:rows table) idx)
|
||||
add-new-object! (when (fn? add-new-object!)
|
||||
(fn [_]
|
||||
(add-new-object! view-entity table
|
||||
{:properties {(:db/ident group-by-property) (or (and (map? value) (:db/id value)) value)}})))
|
||||
table' (shui/table-option (-> table-map
|
||||
(assoc-in [:data-fns :add-new-object!] add-new-object!)
|
||||
(assoc :data group ; data for this group
|
||||
)))
|
||||
readable-property-value #(cond (and (map? %) (or (:block/title %) (:logseq.property/value %)))
|
||||
(db-property/property-value-content %)
|
||||
(= (:db/ident %) :logseq.property/empty-placeholder)
|
||||
(t :ui/empty)
|
||||
:else
|
||||
(str %))
|
||||
group-by-page? (= :block/page group-by-property-ident)
|
||||
key (str (:db/id view-entity) "-group-idx-" idx)]
|
||||
(rum/with-key
|
||||
(group-item view-entity table' group group-by-property value option view-opts
|
||||
{:list-view? list-view?
|
||||
:group-by-page? group-by-page?
|
||||
:readable-property-value readable-property-value})
|
||||
key)))}
|
||||
disable-virtualized?)])
|
||||
(if gallery?
|
||||
(grouped-gallery-view table-map table option view-entity (:rows table) row-selection
|
||||
group-by-property group-by-property-ident *scroller-ref)
|
||||
[:div.flex.flex-col.border-t.pt-2.gap-2
|
||||
(virtualized-list
|
||||
{:class (when list-view? "group-list-view")
|
||||
:custom-scroll-parent (util/app-scroll-container-node (rum/deref *view-ref))
|
||||
:increase-viewport-by {:top 300 :bottom 300}
|
||||
:compute-item-key (fn [idx]
|
||||
(str "table-group" idx))
|
||||
:skipAnimationFrameInResizeObserver true
|
||||
:total-count (count (:rows table))
|
||||
:item-content (fn [idx]
|
||||
(let [[value group] (nth (:rows table) idx)
|
||||
add-new-object! (when (fn? add-new-object!)
|
||||
(fn [_]
|
||||
(add-new-object! view-entity table
|
||||
{:properties {(:db/ident group-by-property) (or (and (map? value) (:db/id value)) value)}})))
|
||||
table' (shui/table-option (-> table-map
|
||||
(assoc-in [:data-fns :add-new-object!] add-new-object!)
|
||||
(assoc :data group)))
|
||||
key (str (:db/id view-entity) "-group-idx-" idx)]
|
||||
(rum/with-key
|
||||
(group-item view-entity table' group group-by-property value option view-opts
|
||||
{:list-view? list-view?
|
||||
:group-by-page? (= :block/page group-by-property-ident)
|
||||
:readable-property-value group-readable-property-value})
|
||||
key)))}
|
||||
disable-virtualized?)]))
|
||||
(view-cp view-entity table
|
||||
(assoc option
|
||||
:group-by-property-ident group-by-property-ident
|
||||
|
||||
@@ -150,7 +150,12 @@
|
||||
["65.29" {:fix add-single-block-comment-targets}]
|
||||
["65.30" {:properties [:logseq.property/assignee]}]
|
||||
["65.31" {:properties [:logseq.property.agent/session-id]}]
|
||||
["65.32" {:fix repair-comment-classes-and-targets}]])
|
||||
["65.32" {:fix repair-comment-classes-and-targets}]
|
||||
["65.33" {:properties [:logseq.property.view/gallery-asset-property
|
||||
:logseq.property.view/gallery-display-properties
|
||||
:logseq.property.view/gallery-card-size
|
||||
:logseq.property.view/gallery-card-width
|
||||
:logseq.property.view/gallery-card-height]}]])
|
||||
|
||||
(let [[major minor] (last (sort (map (comp (juxt :major :minor) db-schema/parse-schema-version first)
|
||||
schema-version->updates)))]
|
||||
|
||||
@@ -1915,6 +1915,15 @@
|
||||
:view.filter/to "to"
|
||||
:view.filter/type-to-search "Type to search"
|
||||
|
||||
:view.gallery/asset-property "Asset property"
|
||||
:view.gallery/card-size "Card size"
|
||||
:view.gallery/compact-size "Compact"
|
||||
:view.gallery/custom-size "Custom"
|
||||
:view.gallery/default-size "Default"
|
||||
:view.gallery/display-properties "Display properties"
|
||||
:view.gallery/height "Height"
|
||||
:view.gallery/width "Width"
|
||||
|
||||
:view.table/ascending "Ascending"
|
||||
:view.table/columns-visibility "Columns visibility"
|
||||
:view.table/default-title (fn [total] (str total (if (<= total 1) " Node" " Nodes")))
|
||||
|
||||
@@ -1897,6 +1897,15 @@
|
||||
:view.filter/to "到"
|
||||
:view.filter/type-to-search "输入以搜索"
|
||||
|
||||
:view.gallery/asset-property "资源属性"
|
||||
:view.gallery/card-size "卡片尺寸"
|
||||
:view.gallery/compact-size "紧凑"
|
||||
:view.gallery/custom-size "自定义"
|
||||
:view.gallery/default-size "默认"
|
||||
:view.gallery/display-properties "显示属性"
|
||||
:view.gallery/height "高度"
|
||||
:view.gallery/width "宽度"
|
||||
|
||||
:view.table/ascending "升序"
|
||||
:view.table/columns-visibility "列可见性"
|
||||
:view.table/default-title "{1} 个节点"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
(ns frontend.components.views-test
|
||||
(:require [cljs.test :refer [deftest is]]
|
||||
[frontend.db]
|
||||
[frontend.components.views :as views]))
|
||||
|
||||
(deftest build-columns-should-allow-name-property-when-no-object-name
|
||||
@@ -38,3 +39,36 @@
|
||||
ordered-ids [:c :b :a :c :b]
|
||||
sorted (views/sort-columns columns ordered-ids)]
|
||||
(is (= [:c :b :a] (map :id sorted)))))
|
||||
|
||||
(deftest gallery-lazy-item-opts-should-request-view-properties
|
||||
(let [properties [:block/title :user.property/cover :block/uuid]]
|
||||
(is (= {:properties properties}
|
||||
(views/gallery-lazy-item-opts {:properties properties})))))
|
||||
|
||||
(deftest gallery-card-asset-block-should-use-row-for-asset-class
|
||||
(let [block {:db/id 1
|
||||
:block/title "Inception poster"
|
||||
:block/uuid #uuid "11111111-1111-1111-1111-111111111111"}]
|
||||
(is (= block
|
||||
(views/gallery-card-asset-block block :block/uuid)))))
|
||||
|
||||
(deftest view-row-ids-should-flatten-grouped-rows
|
||||
(is (= [1 2 3 4]
|
||||
(#'views/view-row-ids
|
||||
[[:group-a [1 2]]
|
||||
[:group-b [3 4]]]))))
|
||||
|
||||
(deftest grouped-gallery-row-ids-should-deduplicate-grouped-rows
|
||||
(is (= [1 2 3]
|
||||
(#'views/grouped-gallery-row-ids
|
||||
[[:group-a [1 2]]
|
||||
[:group-b [{:db/id 2} 3]]]))))
|
||||
|
||||
(deftest group-by-column-should-exclude-name-and-include-many-properties
|
||||
(with-redefs [frontend.db/entity (fn [id]
|
||||
(case id
|
||||
:block/title {:logseq.property/type :string}
|
||||
:block/tags {:logseq.property/type :class
|
||||
:db/cardinality :db.cardinality/many}))]
|
||||
(is (not (views/group-by-column? {:id :block/title})))
|
||||
(is (views/group-by-column? {:id :block/tags}))))
|
||||
|
||||
@@ -285,3 +285,36 @@
|
||||
(is (= #{(:db/id (d/entity migration-db [:block/uuid target-uuid]))}
|
||||
(ref-ids (:logseq.property.comments/blocks
|
||||
(d/entity migration-db [:block/uuid comments-area-uuid]))))))))
|
||||
|
||||
(deftest migrate-65-33-adds-gallery-view-properties
|
||||
(let [conn (d/create-conn db-schema/schema)
|
||||
property-idents [:logseq.property.view/gallery-asset-property
|
||||
:logseq.property.view/gallery-display-properties
|
||||
:logseq.property.view/gallery-card-size
|
||||
:logseq.property.view/gallery-card-width
|
||||
:logseq.property.view/gallery-card-height]]
|
||||
(d/transact! conn [{:db/ident :logseq.kv/schema-version
|
||||
:kv/value {:major 65 :minor 32}}])
|
||||
|
||||
(is (every? nil? (map #(d/entity @conn %) property-idents)))
|
||||
|
||||
(db-migrate/migrate conn :target-version {:major 65 :minor 33})
|
||||
|
||||
(is (= {:major 65 :minor 33}
|
||||
(:kv/value (d/entity @conn :logseq.kv/schema-version))))
|
||||
(is (every? some? (map #(d/entity @conn %) property-idents)))
|
||||
(is (= :property
|
||||
(:logseq.property/type
|
||||
(d/entity @conn :logseq.property.view/gallery-asset-property))))
|
||||
(is (= :db.cardinality/many
|
||||
(:db/cardinality
|
||||
(d/entity @conn :logseq.property.view/gallery-display-properties))))
|
||||
(is (= :keyword
|
||||
(:logseq.property/type
|
||||
(d/entity @conn :logseq.property.view/gallery-card-size))))
|
||||
(is (= :default
|
||||
(:logseq.property/scalar-default-value
|
||||
(d/entity @conn :logseq.property.view/gallery-card-size))))
|
||||
(is (every? #(= :raw-number (:logseq.property/type (d/entity @conn %)))
|
||||
[:logseq.property.view/gallery-card-width
|
||||
:logseq.property.view/gallery-card-height]))))
|
||||
|
||||
Reference in New Issue
Block a user