Browse Source

wip: add global :block/order for properties

Tienson Qin 1 year ago
parent
commit
dda5f2200e

+ 2 - 1
deps/db/package.json

@@ -6,7 +6,8 @@
     "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v11"
   },
   "dependencies": {
-    "better-sqlite3": "9.3.0"
+    "better-sqlite3": "9.3.0",
+    "fractional-indexing": "^3.2.0"
   },
   "scripts": {
     "test": "yarn nbb-logseq -cp test -m nextjournal.test-runner"

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

@@ -279,6 +279,7 @@
   [[:block/content :string]
    [:block/left :int]
    [:block/parent :int]
+   [:block/order {:optional true} :string]
    ;; refs
    [:block/page :int]
    [:block/path-refs {:optional true} [:set :int]]

+ 35 - 0
deps/db/src/logseq/db/frontend/order.cljs

@@ -0,0 +1,35 @@
+(ns logseq.db.frontend.order
+  "Use fractional-indexing order for blocks/properties/closed values/etc."
+  (:require ["fractional-indexing" :as index]
+            [goog.object :as gobj]
+            [datascript.core :as d]))
+
+(defonce *max-key (atom nil))
+
+(defn reset-max-key!
+  [key]
+  (when (and key (or (nil? @*max-key)
+                     (> (compare key @*max-key) 0)))
+    (reset! *max-key key)))
+
+(defn gen-key
+  ([end]
+   (gen-key @*max-key end))
+  ([start end]
+   (let [k ((gobj/get index "generateKeyBetween") start end)]
+    (reset-max-key! k))))
+
+(defn get-max-order
+  [db]
+  (:v (first (d/rseek-datoms db :avet :block/order))))
+
+(comment
+  (defn gen-n-keys
+    [n start end]
+    (let [ks (js->clj ((gobj/get index "generateNKeysBetween") start end n))]
+      (reset! *max-key (last ks))
+      ks))
+
+  (defn get-next-order
+    [db current-key]
+    (:v (first (d/seek-datoms db :avet :block/order current-key)))))

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

@@ -123,6 +123,7 @@
            :block/namespace :block/properties-text-values :block/pre-block? :recent/pages :file/handle :block/file
            :block/properties :block/properties-order :block/repeated? :block/deadline :block/scheduled :block/priority :block/marker)
    {:block/name {:db/index true}        ; remove db/unique for :block/name
+    :block/order {:db/index true}
     ;; class properties
     :class/parent {:db/valueType :db.type/ref
                    :db/index true}

+ 4 - 1
deps/db/src/logseq/db/sqlite/common_db.cljs

@@ -8,7 +8,8 @@
             [logseq.common.util :as common-util]
             [logseq.common.config :as common-config]
             [logseq.db.frontend.entity-plus :as entity-plus]
-            [clojure.set :as set]))
+            [clojure.set :as set]
+            [logseq.db.frontend.order :as db-order]))
 
 (defn- get-pages-by-name
   [db page-name]
@@ -232,6 +233,8 @@
 (defn get-initial-data
   "Returns current database schema and initial data"
   [db]
+  (let [max-key (db-order/get-max-order db)]
+    (db-order/reset-max-key! max-key))
   (let [schema (:schema db)
         idents (mapcat (fn [id]
                          (when-let [e (d/entity db id)]

+ 9 - 4
deps/db/src/logseq/db/sqlite/util.cljs

@@ -9,7 +9,8 @@
             [datascript.core :as d]
             [cljs-bean.transit]
             [logseq.db.frontend.property.type :as db-property-type]
-            [logseq.db.frontend.property :as db-property]))
+            [logseq.db.frontend.property :as db-property]
+            [logseq.db.frontend.order :as db-order]))
 
 (defonce db-version-prefix "logseq_db_")
 (defonce file-version-prefix "logseq_local_")
@@ -70,14 +71,16 @@
   "Build a standard new property so that it is is consistent across contexts. Takes
    an optional map with following keys:
    * :original-name - Case sensitive property name. Defaults to deriving this from db-ident
-   * :block-uuid - :block/uuid for property"
+   * :block-uuid - :block/uuid for property
+   * :from-ui-thread? - whether calls from the UI thread"
   ([db-ident prop-schema] (build-new-property db-ident prop-schema {}))
-  ([db-ident prop-schema {:keys [original-name block-uuid ref-type?]}]
+  ([db-ident prop-schema {:keys [original-name block-uuid ref-type? from-ui-thread?]}]
    (assert (keyword? db-ident))
    (let [db-ident' (if (qualified-keyword? db-ident)
                      db-ident
                      (db-property/create-user-property-ident-from-name (name db-ident)))
-         prop-name (or original-name (name db-ident'))]
+         prop-name (or original-name (name db-ident'))
+         block-order (when-not from-ui-thread? (db-order/gen-key nil))]
      (block-with-timestamps
       (cond->
        {:db/ident db-ident'
@@ -91,6 +94,8 @@
         :db/cardinality (if (= :many (:cardinality prop-schema))
                           :db.cardinality/many
                           :db.cardinality/one)}
+        block-order
+        (assoc :block/order block-order)
         (or ref-type? (contains? (conj db-property-type/ref-property-types :entity) (:type prop-schema)))
         (assoc :db/valueType :db.type/ref))))))
 

+ 5 - 0
deps/db/yarn.lock

@@ -84,6 +84,11 @@ [email protected]:
   resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
   integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
 
+fractional-indexing@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/fractional-indexing/-/fractional-indexing-3.2.0.tgz#1193e63d54ff4e0cbe0c79a9ed6cfbab25d91628"
+  integrity sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ==
+
 fs-constants@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"

+ 1 - 0
package.json

@@ -123,6 +123,7 @@
         "electron": "27.1.3",
         "electron-dl": "3.3.0",
         "emoji-mart": "^5.5.2",
+        "fractional-indexing": "^3.2.0",
         "fs": "0.0.1-security",
         "fs-extra": "9.1.0",
         "fuse.js": "6.4.6",

+ 17 - 13
src/main/frontend/components/property.cljs

@@ -717,19 +717,23 @@
   [block properties opts]
   (let [class? (:class-schema? opts)]
     (when (seq properties)
-      (if class?
-        (let [choices (map (fn [[k v]]
-                             {:id (str k)
-                              :value k
-                              :content (property-cp block k v opts)}) properties)]
-          (dnd/items choices
-                     {:on-drag-end (fn [properties]
-                                     (let [schema (assoc (:block/schema block)
-                                                         :properties properties)]
-                                       (when (seq properties)
-                                         (db-property-handler/class-set-schema! (state/get-current-repo) (:block/uuid block) schema))))}))
-        (for [[k v] properties]
-          (property-cp block k v opts))))))
+      ;; Sort properties by :block/order
+      (let [properties' (sort-by (fn [[k _v]]
+                                   (:block/order (db/entity k))) properties)]
+        (if class?
+          (let [choices (map (fn [[k v]]
+                               {:id (str k)
+                                :value k
+                                :content (property-cp block k v opts)}) properties')]
+            (dnd/items choices
+                       {:on-drag-end (fn [properties]
+                                       (let [schema (assoc (:block/schema block)
+                                                           :properties properties)]
+                                         (when (seq properties)
+                                           (db-property-handler/class-set-schema! (state/get-current-repo) (:block/uuid block) schema))))}))
+          ;; TODO: support drag && drop
+          (for [[k v] properties']
+            (property-cp block k v opts)))))))
 
 (defn- async-load-classes!
   [block]

+ 1 - 1
src/main/frontend/components/property/closed_value.cljs

@@ -24,7 +24,7 @@
 (defn- <upsert-closed-value!
   "Create new closed value and returns its block UUID."
   [property item]
-  (p/let [{:keys [block-id tx-data]} (db-property-handler/upsert-closed-value property item)]
+  (p/let [{:keys [block-id tx-data]} (db-property-handler/<upsert-closed-value property item)]
     (p/do!
      (when (seq tx-data) (db/transact! tx-data))
      (when (seq tx-data) (db-property-handler/re-init-commands! property))

+ 8 - 1
src/main/frontend/db_worker.cljs

@@ -32,7 +32,8 @@
             [promesa.core :as p]
             [shadow.cljs.modern :refer [defclass]]
             [logseq.common.util :as common-util]
-            [frontend.worker.db.fix :as db-fix]))
+            [frontend.worker.db.fix :as db-fix]
+            [logseq.db.frontend.order :as db-order]))
 
 (defonce *sqlite worker-state/*sqlite)
 (defonce *sqlite-conns worker-state/*sqlite-conns)
@@ -368,6 +369,12 @@
                         (concat tx-data
                                 (db-fix/fix-cardinality-many->one @conn (:property-id tx-meta)))
                         tx-data)
+             tx-data' (if (= :new-property (:outliner-op tx-meta))
+                        (map (fn [m]
+                               (if (and (map? m))
+                                 (assoc m :block/order (db-order/gen-key nil))
+                                 m)) tx-data')
+                        tx-data')
              context (if (string? context)
                        (ldb/read-transit-str context)
                        context)

+ 3 - 2
src/main/frontend/handler/db_based/property.cljs

@@ -162,7 +162,8 @@
         (assert (some? k-name)
                 (prn "property-id: " property-id ", property-name: " property-name))
         (db/transact! repo
-                      [(sqlite-util/build-new-property db-ident' schema {:original-name k-name})]
+                      [(sqlite-util/build-new-property db-ident' schema {:original-name k-name
+                                                                         :from-ui-thread? true})]
                       {:outliner-op :new-property})))))
 
 (defn validate-property-value
@@ -581,7 +582,7 @@
                     :block/schema schema}]
                   {:outliner-op :save-block})))
 
-(defn upsert-closed-value
+(defn <upsert-closed-value
   "id should be a block UUID or nil"
   [property {:keys [id value icon description]
              :or {description ""}}]

+ 4 - 4
src/test/frontend/handler/db_based/property_closed_value_test.cljs

@@ -53,17 +53,17 @@
            (is (every? #(contains? (:block/type (db/entity [:block/uuid %])) "closed value")
                        values))))
        (testing "Add non-numbers shouldn't work"
-         (p/let [result (db-property-handler/upsert-closed-value property {:value "not a number"})]
+         (p/let [result (db-property-handler/<upsert-closed-value property {:value "not a number"})]
            (is (= result :value-invalid))
            (let [values (get-value-ids k)]
              (is (= #{1 2} (get-closed-values values))))))
 
        (testing "Add existing value"
-         (p/let [result (db-property-handler/upsert-closed-value property {:value 2})]
+         (p/let [result (db-property-handler/<upsert-closed-value property {:value 2})]
            (is (= result :value-exists))))
 
        (testing "Add new value"
-         (p/let [{:keys [block-id tx-data]} (db-property-handler/upsert-closed-value property {:value 3})]
+         (p/let [{:keys [block-id tx-data]} (db-property-handler/<upsert-closed-value property {:value 3})]
            (db/transact! tx-data)
            (let [b (db/entity [:block/uuid block-id])]
              (is (= 3 (:value (:block/schema b))))
@@ -72,7 +72,7 @@
                (is (= #{1 2 3} (get-closed-values values))))
 
              (testing "Update closed value"
-               (p/let [{:keys [tx-data]} (db-property-handler/upsert-closed-value property {:id block-id
+               (p/let [{:keys [tx-data]} (db-property-handler/<upsert-closed-value property {:id block-id
                                                                                             :value 4
                                                                                             :description "choice 4"})]
                  (db/transact! tx-data)

+ 5 - 0
yarn.lock

@@ -3254,6 +3254,11 @@ fraction.js@^4.3.6:
   resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.6.tgz#e9e3acec6c9a28cf7bc36cbe35eea4ceb2c5c92d"
   integrity sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==
 
+fractional-indexing@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/fractional-indexing/-/fractional-indexing-3.2.0.tgz#1193e63d54ff4e0cbe0c79a9ed6cfbab25d91628"
+  integrity sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ==
+
 fragment-cache@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"