Просмотр исходного кода

enhance(ux): store image width/height to avoid scrolling junk

related to https://github.com/logseq/db-test/issues/562
related to https://github.com/logseq/db-test/issues/488
Tienson Qin 4 месяцев назад
Родитель
Сommit
1fca2a9ff6

+ 3 - 1
deps/db/src/logseq/db/frontend/malli_schema.cljs

@@ -483,7 +483,9 @@
     ;; TODO: Derive required property types from existing schema in frontend.property
     [[:logseq.property.asset/type :string]
      [:logseq.property.asset/checksum :string]
-     [:logseq.property.asset/size :int]]
+     [:logseq.property.asset/size :int]
+     [:logseq.property.asset/width {:optional true} :int]
+     [:logseq.property.asset/height {:optional true} :int]]
     block-attrs
     page-or-block-attrs)))
 

+ 10 - 0
deps/db/src/logseq/db/frontend/property.cljs

@@ -492,6 +492,16 @@
                                            :hide? true
                                            :public? false}
                                   :queryable? true}
+     :logseq.property.asset/width {:title "Image width"
+                                   :schema {:type :raw-number
+                                            :hide? true
+                                            :public? false}
+                                   :queryable? true}
+     :logseq.property.asset/height {:title "Image height"
+                                    :schema {:type :raw-number
+                                             :hide? true
+                                             :public? false}
+                                    :queryable? true}
      :logseq.property.asset/checksum {:title "File checksum"
                                       :schema {:type :string
                                                :hide? true

+ 1 - 1
deps/db/src/logseq/db/frontend/schema.cljs

@@ -37,7 +37,7 @@
          (map (juxt :major :minor)
               [(parse-schema-version x) (parse-schema-version y)])))
 
-(def version (parse-schema-version "65.12"))
+(def version (parse-schema-version "65.13"))
 
 (defn major-version
   "Return a number.

+ 153 - 116
src/main/frontend/components/block.cljs

@@ -51,6 +51,7 @@
             [frontend.handler.file-sync :as file-sync]
             [frontend.handler.notification :as notification]
             [frontend.handler.plugin :as plugin-handler]
+            [frontend.handler.property :as property-handler]
             [frontend.handler.property.file :as property-file]
             [frontend.handler.property.util :as pu]
             [frontend.handler.route :as route-handler]
@@ -301,106 +302,127 @@
      [:span.handle-left.image-resize (assoc handle-props :ref *handle-left)]
      [:span.handle-right.image-resize (assoc handle-props :ref *handle-right)]]))
 
+(defn measure-image! [url on-dimensions]
+  (let [img (js/Image.)]
+    (set! (.-onload img)
+          (fn []
+            (on-dimensions (.-naturalWidth img) (.-naturalHeight img))))
+    (set! (.-src img) url)))
+
 (defonce *resizing-image? (atom false))
-(rum/defc asset-container
+(rum/defc ^:large-vars/cleanup-todo asset-container
   [asset-block src title metadata {:keys [breadcrumb? positioned? local? full-text]}]
-  (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 "~"))
-               (str "file://" src)
-               src)
-        get-blockid #(some-> (rum/deref *el-ref) (.closest "[blockid]") (.getAttribute "blockid") (uuid))]
-    [:div.asset-container
-     {:key "resize-asset-container"
-      :on-pointer-down util/stop
-      :on-click (fn [e]
-                  (util/stop e)
-                  (when (= "IMG" (some-> (.-target e) (.-nodeName)))
-                    (open-lightbox! e)))
-      :ref *el-ref}
-     [:img.rounded-sm.relative
-      (merge
-       {:loading "lazy"
-        :referrerPolicy "no-referrer"
-        :src src'
-        :title title}
-       metadata)]
-     (when (and (not breadcrumb?)
-                (not positioned?))
-       [:<>
-        (let [handle-copy!
-              (fn [_e]
-                (-> (util/copy-image-to-clipboard image-src)
-                    (p/then #(notification/show! "Copied!" :success))))
-              handle-delete!
-              (fn [_e]
-                (when-let [block-id (get-blockid)]
-                  (let [*local-selected? (atom local?)]
-                    (-> (shui/dialog-confirm!
-                         [:div.text-xs.opacity-60.-my-2
-                          (when (and local? (not= (:block/uuid asset-block) block-id))
-                            [:label.flex.gap-1.items-center
-                             (shui/checkbox
-                              {:default-checked @*local-selected?
-                               :on-checked-change #(reset! *local-selected? %)})
-                             (t :asset/physical-delete)])]
-                         {:title (t :asset/confirm-delete (.toLocaleLowerCase (t :text/image)))
-                          :outside-cancel? true})
-                        (p/then (fn []
-                                  (shui/dialog-close!)
-                                  (editor-handler/delete-asset-of-block!
-                                   {:block-id block-id
-                                    :asset-block asset-block
-                                    :local? local?
-                                    :delete-local? @*local-selected?
-                                    :repo (state/get-current-repo)
-                                    :href src
-                                    :title title
-                                    :full-text full-text})))))))]
-          [:.asset-action-bar {:aria-hidden "true"}
-           (shui/button-group
-            (shui/button
-             {:variant :outline
-              :size :icon
-              :class "h-7 w-7"
-              :on-pointer-down util/stop
-              :on-click (fn [e]
-                          (shui/popup-show! (.closest (.-target e) ".asset-action-bar")
-                                            (fn []
-                                              [:div
-                                               {:on-click #(shui/popup-hide!)}
-                                               (shui/dropdown-menu-item
-                                                {:on-click #(some-> (db/entity [:block/uuid (get-blockid)])
-                                                                    (editor-handler/edit-block! :max {:container-id :unknown-container}))}
-                                                [:span.flex.items-center.gap-1
-                                                 (ui/icon "edit") (t :asset/edit-block)])
-                                               (shui/dropdown-menu-item
-                                                {:on-click handle-copy!}
-                                                [:span.flex.items-center.gap-1
-                                                 (ui/icon "copy") (t :asset/copy)])
-                                               (when (util/electron?)
+  (let [asset-width (:logseq.property.asset/width asset-block)
+        asset-height (:logseq.property.asset/height asset-block)]
+    (hooks/use-effect!
+     (fn []
+       (when-not (or asset-width asset-height)
+         (measure-image!
+          src
+          (fn [width height]
+            (when (nil? (:logseq.property.asset/width asset-block))
+              (property-handler/set-block-properties! (state/get-current-repo)
+                                                      (:block/uuid asset-block)
+                                                      {:logseq.property.asset/width width
+                                                       :logseq.property.asset/height height})))))
+       (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 "~"))
+                 (str "file://" src)
+                 src)
+          get-blockid #(some-> (rum/deref *el-ref) (.closest "[blockid]") (.getAttribute "blockid") (uuid))]
+      [:div.asset-container
+       {:key "resize-asset-container"
+        :on-pointer-down util/stop
+        :on-click (fn [e]
+                    (util/stop e)
+                    (when (= "IMG" (some-> (.-target e) (.-nodeName)))
+                      (open-lightbox! e)))
+        :ref *el-ref}
+       [:img.rounded-sm.relative.fade-in.fade-in-faster
+        (merge
+         {:loading "lazy"
+          :referrerPolicy "no-referrer"
+          :src src'
+          :title title}
+         metadata)]
+       (when (and (not breadcrumb?)
+                  (not positioned?))
+         [:<>
+          (let [handle-copy!
+                (fn [_e]
+                  (-> (util/copy-image-to-clipboard image-src)
+                      (p/then #(notification/show! "Copied!" :success))))
+                handle-delete!
+                (fn [_e]
+                  (when-let [block-id (get-blockid)]
+                    (let [*local-selected? (atom local?)]
+                      (-> (shui/dialog-confirm!
+                           [:div.text-xs.opacity-60.-my-2
+                            (when (and local? (not= (:block/uuid asset-block) block-id))
+                              [:label.flex.gap-1.items-center
+                               (shui/checkbox
+                                {:default-checked @*local-selected?
+                                 :on-checked-change #(reset! *local-selected? %)})
+                               (t :asset/physical-delete)])]
+                           {:title (t :asset/confirm-delete (.toLocaleLowerCase (t :text/image)))
+                            :outside-cancel? true})
+                          (p/then (fn []
+                                    (shui/dialog-close!)
+                                    (editor-handler/delete-asset-of-block!
+                                     {:block-id block-id
+                                      :asset-block asset-block
+                                      :local? local?
+                                      :delete-local? @*local-selected?
+                                      :repo (state/get-current-repo)
+                                      :href src
+                                      :title title
+                                      :full-text full-text})))))))]
+            [:.asset-action-bar {:aria-hidden "true"}
+             (shui/button-group
+              (shui/button
+               {:variant :outline
+                :size :icon
+                :class "h-7 w-7"
+                :on-pointer-down util/stop
+                :on-click (fn [e]
+                            (shui/popup-show! (.closest (.-target e) ".asset-action-bar")
+                                              (fn []
+                                                [:div
+                                                 {:on-click #(shui/popup-hide!)}
                                                  (shui/dropdown-menu-item
-                                                  {:on-click (fn [e]
-                                                               (util/stop e)
-                                                               (if local?
-                                                                 (ipc/ipc "openFileInFolder" image-src)
-                                                                 (js/window.apis.openExternal image-src)))}
+                                                  {:on-click #(some-> (db/entity [:block/uuid (get-blockid)])
+                                                                      (editor-handler/edit-block! :max {:container-id :unknown-container}))}
                                                   [:span.flex.items-center.gap-1
-                                                   (ui/icon "folder-pin") (t (if local? :asset/show-in-folder :asset/open-in-browser))]))
-
-                                               (when-not config/publishing?
-                                                 [:<>
-                                                  (shui/dropdown-menu-separator)
-                                                  (shui/dropdown-menu-item
-                                                   {:on-click handle-delete!}
-                                                   [:span.flex.items-center.gap-1.text-red-700
-                                                    (ui/icon "trash") (t :asset/delete)])])])
-                                            {:align :start
-                                             :dropdown-menu? true}))}
-             (shui/tabler-icon "dots-vertical")))])])]))
-
-;; TODO: store image height and width for better ux
+                                                   (ui/icon "edit") (t :asset/edit-block)])
+                                                 (shui/dropdown-menu-item
+                                                  {:on-click handle-copy!}
+                                                  [:span.flex.items-center.gap-1
+                                                   (ui/icon "copy") (t :asset/copy)])
+                                                 (when (util/electron?)
+                                                   (shui/dropdown-menu-item
+                                                    {:on-click (fn [e]
+                                                                 (util/stop e)
+                                                                 (if local?
+                                                                   (ipc/ipc "openFileInFolder" image-src)
+                                                                   (js/window.apis.openExternal image-src)))}
+                                                    [:span.flex.items-center.gap-1
+                                                     (ui/icon "folder-pin") (t (if local? :asset/show-in-folder :asset/open-in-browser))]))
+
+                                                 (when-not config/publishing?
+                                                   [:<>
+                                                    (shui/dropdown-menu-separator)
+                                                    (shui/dropdown-menu-item
+                                                     {:on-click handle-delete!}
+                                                     [:span.flex.items-center.gap-1.text-red-700
+                                                      (ui/icon "trash") (t :asset/delete)])])])
+                                              {:align :start
+                                               :dropdown-menu? true}))}
+               (shui/tabler-icon "dots-vertical")))])])])))
+
 (rum/defcs ^:large-vars/cleanup-todo resizable-image <
   (rum/local nil ::size)
   {:will-unmount (fn [state]
@@ -410,16 +432,10 @@
   (let [breadcrumb? (:breadcrumb? config)
         positioned? (:property-position config)
         asset-block (:asset-block config)
-        width (or (get-in asset-block [:logseq.property.asset/resize-metadata :width])
-                  (:width metadata))
+        width (:width metadata)
         *width (get state ::size)
-        width (or @*width width 250)
-        metadata' (merge
-                   (cond->
-                    {:height 125}
-                     width
-                     (assoc :width width))
-                   metadata)
+        width (or @*width width)
+        metadata' (assoc metadata :width width)
         resizable? (and (not (mobile-util/native-platform?))
                         (not breadcrumb?)
                         (not positioned?))
@@ -503,14 +519,13 @@
         repo (state/get-current-repo)
         href (config/get-local-asset-absolute-path href)
         db-based? (config/db-based-graph? repo)]
-    (when (and (or db-based?
-                   (util/electron?)
-                   (mobile-util/native-platform?))
-               (nil? @src))
+
+    (when (nil? @src)
       (p/then (assets-handler/<make-asset-url href)
               #(reset! src (common-util/safe-decode-uri-component %))))
-
-    (when @src
+    (:image-placeholder config)
+    (if-not @src
+      (:image-placeholder config)
       (let [ext (keyword (or (util/get-file-ext @src)
                              (util/get-file-ext href)))
             repo (state/get-current-repo)
@@ -1074,13 +1089,35 @@
         file-exists? @(::file-exists? state)
         repo (state/get-current-repo)
         asset-file-write-finished? (state/sub :assets/asset-file-write-finish
-                                              {:path-in-sub-atom [repo (str (:block/uuid block))]})]
-    (when (or file-exists? asset-file-write-finished?)
-      (asset-link (assoc config :asset-block block)
+                                              {:path-in-sub-atom [repo (str (:block/uuid block))]})
+        asset-type (:logseq.property.asset/type block)
+        image? (contains? (common-config/img-formats) (keyword asset-type))
+        width (get-in block [:logseq.property.asset/resize-metadata :width])
+        asset-width (:logseq.property.asset/width block)
+        asset-height (:logseq.property.asset/height block)
+        img-metadata (when image?
+                       (let [width (or width 250 asset-width)
+                             aspect-ratio (when (and asset-width asset-height)
+                                            (/ asset-width asset-height))]
+                         (merge
+                          (when width
+                            {:width width})
+                          (when (and width aspect-ratio)
+                            {:height (/ width aspect-ratio)}))))
+        img-placeholder (when image?
+                          [:div.img-placeholder.asset-container
+                           {:style img-metadata}])]
+    (cond
+      (or file-exists? asset-file-write-finished?)
+      (asset-link (assoc config
+                         :asset-block block
+                         :image-placeholder img-placeholder)
                   (:block/title block)
                   (path/path-join (str "../" common-config/local-assets-dir) file)
-                  nil
-                  nil))))
+                  img-metadata
+                  nil)
+      image?
+      img-placeholder)))
 
 (defn- img-audio-video?
   [block]

+ 3 - 1
src/main/frontend/worker/db/migrate.cljs

@@ -430,7 +430,9 @@
    ["65.9" {:properties [:logseq.property.embedding/hnsw-label-updated-at]}]
    ["65.10" {:properties [:block/journal-day :logseq.property.view/sort-groups-by-property :logseq.property.view/sort-groups-desc?]}]
    ["65.11" {:fix remove-block-path-refs}]
-   ["65.12" {:fix remove-position-property-from-url-properties}]])
+   ["65.12" {:fix remove-position-property-from-url-properties}]
+   ["65.13" {:properties [:logseq.property.asset/width
+                          :logseq.property.asset/height]}]])
 
 (let [[major minor] (last (sort (map (comp (juxt :major :minor) db-schema/parse-schema-version first)
                                      schema-version->updates)))]

+ 6 - 6
src/main/logseq/api/db_based.cljs

@@ -219,8 +219,8 @@
         (sdk-utils/result->js tag)))))
 
 (defn tag-add-property [tag-id property-id-or-name]
-  (p/let [tag (db/get-page tag-id)
-          property (db/get-page property-id-or-name)]
+  (p/let [tag (db/get-case-page tag-id)
+          property (db/get-case-page property-id-or-name)]
     (when-not (ldb/class? tag) (throw (ex-info "Not a valid tag" {:tag tag-id})))
     (when-not (ldb/property? property) (throw (ex-info "Not a valid property" {:property property-id-or-name})))
     (when (and (not (ldb/public-built-in-property? property))
@@ -228,16 +228,16 @@
       (throw (ex-info "This is a private built-in property that can't be used." {:value property})))
     (p/do!
      (db-property-handler/class-add-property! (:db/id tag) (:db/ident property))
-     (sdk-utils/result->js (db/get-page tag-id)))))
+     (sdk-utils/result->js (db/get-case-page tag-id)))))
 
 (defn tag-remove-property [tag-id property-id-or-name]
-  (p/let [tag (db/get-page tag-id)
-          property (db/get-page property-id-or-name)]
+  (p/let [tag (db/get-case-page tag-id)
+          property (db/get-case-page property-id-or-name)]
     (when-not (ldb/class? tag) (throw (ex-info "Not a valid tag" {:tag tag-id})))
     (when-not (ldb/property? property) (throw (ex-info "Not a valid property" {:property property-id-or-name})))
     (p/do!
      (db-property-handler/class-remove-property! (:db/id tag) (:db/ident property))
-     (sdk-utils/result->js (db/get-page tag-id)))))
+     (sdk-utils/result->js (db/get-case-page tag-id)))))
 
 (defn add-block-tag [id-or-name tag-id]
   (p/let [repo (state/get-current-repo)