Browse Source

enhance: move enum property choices to hidden page

related to LOG-2871
Tienson Qin 2 years ago
parent
commit
8c7098b21f

+ 10 - 13
deps/db/src/logseq/db/frontend/malli_schema.cljs

@@ -133,18 +133,7 @@
    ;; Just for :enum type
    [:enum-config {:optional true}
     [:map
-     [:values
-      [:map-of
-       :uuid [:map
-              [:name :string]
-              [:description :string]
-              [:icon {:optional true}
-               [:map
-                [:id :string]
-                [:name :string]
-                [:type [:enum :tabler-icon :emoji]]]]]]]
-     ;; Optional b/c built-in props don't have it set
-     [:order {:optional true} [:vector :uuid]]]]
+     [:values [:vector :uuid]]]]
    ;; Just for :enum
    [:position {:optional true} :string]
    ;; For :page and :template
@@ -233,6 +222,14 @@
     block-attrs
     (remove #(= :block/tags (first %)) page-or-block-attrs))))
 
+;; (def enum-value
+;;   "A enum item"
+;;   (vec
+;;    (concat
+;;     [:map]
+;;     [[:block/type [:= #{"enum value"}]]]
+;;     block-attrs)))
+
 (def normal-block
   "A block with content and no special type or tag behavior"
   (vec
@@ -327,4 +324,4 @@
   (when-let [undeclared-attrs (seq (remove (some-fn malli-non-ref-attrs attrs-to-ignore) db-schema/db-non-ref-attributes))]
     (throw (ex-info (str "The malli DB schema is missing the following non ref attributes from datascript's schema: "
                          (string/join ", " undeclared-attrs))
-                    {}))))
+                    {}))))

+ 8 - 10
src/main/frontend/components/property.cljs

@@ -110,6 +110,7 @@
 (rum/defcs ^:large-vars/cleanup-todo property-config <
   shortcut/disable-all-shortcuts
   rum/reactive
+  db-mixins/query
   (rum/local nil ::property-name)
   (rum/local nil ::property-schema)
   {:will-mount (fn [state]
@@ -204,7 +205,7 @@
         [:div.grid.grid-cols-4.gap-1.items-start.leading-8
          [:label.col-span-1 "Enum choices:"]
          [:div.col-span-3
-          (enum/enum-choices property *property-name *property-schema (:enum-config @*property-schema))]]
+          (enum/enum-choices property *property-name *property-schema)]]
 
         nil)
 
@@ -511,7 +512,9 @@
     (db/sub-block (:db/id linked-block))
     (db/sub-block (:db/id block))))
 
-(rum/defc property-cp
+(rum/defc property-cp <
+  rum/reactive
+  db-mixins/query
   [block k v {:keys [inline-text] :as opts}]
   (when (uuid? k)
     (when-let [property (db/sub-block (:db/id (db/entity [:block/uuid k])))]
@@ -576,10 +579,8 @@
        (properties-section block hidden-properties opts))]))
 
 (rum/defcs ^:large-vars/cleanup-todo properties-area < rum/reactive
-  (rum/local false ::hover?)
   [state target-block edit-input-id {:keys [in-block-container? page-configure? class-schema?] :as opts}]
-  (let [*hover? (::hover? state)
-        block (resolve-linked-block-if-exists target-block)
+  (let [block (resolve-linked-block-if-exists target-block)
         block-properties (:block/properties block)
         properties (if (and class-schema? page-configure?)
                      (let [properties (:properties (:block/schema block))]
@@ -640,15 +641,12 @@
                                 (recur (rest classes)
                                        (set/union properties (set cur-properties))
                                        (conj result [class cur-properties])))
-                              result))
-        opts (assoc opts :hover? @*hover?)]
+                              result))]
     (when-not (and (empty? block-own-properties)
                    (empty? class->properties)
                    (not new-property?)
                    (not (:page-configure? opts)))
-      [:div.ls-properties-area (cond->
-                                {:on-mouse-over #(reset! *hover? true)
-                                 :on-mouse-out #(reset! *hover? false)}
+      [:div.ls-properties-area (cond-> {}
                                  (:selected? opts)
                                  (assoc :class "select-none"))
        (properties-section block (if class-schema? properties own-properties) opts)

+ 53 - 64
src/main/frontend/components/property/enum.cljs

@@ -1,14 +1,25 @@
 (ns frontend.components.property.enum
   "Enum property config"
   (:require [rum.core :as rum]
-            [frontend.components.dnd :as dnd]
+            [clojure.string :as string]
             [frontend.modules.shortcut.core :as shortcut]
             [frontend.util :as util]
             [frontend.ui :as ui]
+            [frontend.components.dnd :as dnd]
             [frontend.components.icon :as icon-component]
-            [clojure.string :as string]
+            [frontend.components.property.util :as components-pu]
             [frontend.handler.notification :as notification]
-            [frontend.components.property.util :as components-pu]))
+            [frontend.handler.property :as property-handler]
+            [frontend.db :as db]
+            [frontend.state :as state]
+            [frontend.handler.property.util :as pu]))
+
+(defn- upsert-enum-item!
+  "Create new enum value and returns its block UUID."
+  [property item]
+  (let [{:keys [block-id tx-data]} (property-handler/upsert-enum-item property item)]
+    (when (seq tx-data) (db/transact! tx-data))
+    block-id))
 
 (rum/defc icon
   [icon {:keys [disabled? on-chosen]}]
@@ -30,11 +41,14 @@
 (rum/defcs enum-item-config < rum/reactive
   shortcut/disable-all-shortcuts
   {:init (fn [state]
-           (let [{:keys [name icon description]} (first (:rum/args state))]
-             (assoc state
-                    ::name (atom (or name ""))
-                    ::icon (atom icon)
-                    ::description (atom (or description "")))))}
+           (let [block (first (:rum/args state))]
+             (let [name (or (:block/content block) "")
+                   icon (pu/get-property block :icon)
+                   description (or (get-in block [:block/schema :description]) "")]
+               (assoc state
+                      ::name (atom name)
+                      ::icon (atom icon)
+                      ::description (atom description)))))}
   [state _item {:keys [toggle-fn on-save]}]
   (let [*name (::name state)
         *icon (::icon state)
@@ -63,10 +77,8 @@
        :on-click (fn [e]
                    (util/stop e)
                    (when-not (string/blank? @*name)
-                     (let [result (when on-save (on-save (string/trim @*name) @*icon @*description))]
-                       (if (= :value-exists result)
-                         (notification/show! (str "Choice already exist") :warning)
-                         (when toggle-fn (toggle-fn)))))))]]))
+                     (when on-save (on-save (string/trim @*name) @*icon @*description))
+                     (when toggle-fn (toggle-fn)))))]]))
 
 (rum/defcs enum-new-item <
   (rum/local "" ::name)
@@ -115,7 +127,7 @@
      {:on-mouse-over #(reset! *hover? true)
       :on-mouse-out #(reset! *hover? false)}
      [:div.flex.flex-row.items-center.gap-2
-      (icon (:icon item)
+      (icon (pu/get-property item :icon)
             {:on-chosen (fn [_e icon]
                           (update-icon icon))})
       [:a {:on-click toggle-fn}
@@ -126,66 +138,49 @@
         (ui/icon "X")])]))
 
 (rum/defc choice-item-content
-  [property item values order *property-schema *property-name dropdown-opts]
-  (let [{:keys [id name]} item]
+  [property block dropdown-opts]
+  (let [{:block/keys [uuid content]} block]
     (ui/dropdown
      (fn [opts]
        (choice-with-close
-        item
-        name
+        block
+        content
         (assoc opts
                :delete-choice
                (fn []
-                 (let [new-values (dissoc values id)
-                       new-order (vec (remove #{id} order))]
-                   (swap! *property-schema assoc :enum-config {:values new-values
-                                                               :order new-order})
-                        ;; FIXME: how to handle block properties with this value?
-                        ;; 1. delete the blocks' property that has this value
-                        ;; 2. update exist values to the default value if exists
-                        ;; 3. soft delete, users can still see it in some existing blocks,
-                        ;;    but they will not see it when adding or updating this property
-                   (components-pu/update-property! property @*property-name @*property-schema)))
+                 (property-handler/delete-enum-item property block))
                :update-icon
                (fn [icon]
-                 (let [new-values (assoc-in values [id :icon] icon)]
-                   (swap! *property-schema assoc :enum-config {:values new-values
-                                                               :order order})
-                   (components-pu/update-property! property @*property-name @*property-schema))))))
+                 (property-handler/update-property! (state/get-current-repo)
+                                                    (pu/get-pid "icon")
+                                                    icon)))))
      (fn [opts]
        (enum-item-config
-        item
+        block
         (assoc opts :on-save
                (fn [name icon description]
-                 (if (some (fn [[vid m]] (and (not= vid id) (= name (:name m)))) values)
-                   :value-exists
-                   (let [new-values (assoc values id {:name name
-                                                      :icon icon
-                                                      :description description})]
-                     (swap! *property-schema assoc :enum-config {:values new-values
-                                                                 :order order})
-                     (components-pu/update-property! property @*property-name @*property-schema)))))))
+                 (upsert-enum-item! property {:id uuid
+                                              :name name
+                                              :description description
+                                              :icon icon})))))
      dropdown-opts)))
 
 (rum/defc enum-choices
-  [property *property-name *property-schema {:keys [values order] :as _config}]
-  (let [dropdown-opts {:modal-class (util/hiccup->class
-                                     "origin-top-right.absolute.left-0.rounded-md.shadow-lg")}
-        order (if (not= (count order) (count values))
-                (vec (concat order (remove (set order) (keys values))))
-                order)]
+  [property *property-name *property-schema]
+  (let [values (get-in property [:block/schema :enum-config :values])
+        dropdown-opts {:modal-class (util/hiccup->class
+                                     "origin-top-right.absolute.left-0.rounded-md.shadow-lg")}]
     [:div.enum-choices.flex.flex-col
-     (let [choices (mapv (fn [id]
-                           (let [item (assoc (get values id) :id id)]
+     (let [choices (keep (fn [id]
+                           (when-let [block (db/entity [:block/uuid id])]
                              {:id (str id)
                               :value id
-                              :content (choice-item-content property item values order *property-schema *property-name dropdown-opts)}))
-                         order)]
+                              :content (choice-item-content property block dropdown-opts)}))
+                         values)]
        (dnd/items choices
-                  {:on-drag-end (fn [new-order]
-                                  (when (seq new-order)
-                                    (swap! *property-schema assoc :enum-config {:values values
-                                                                                :order new-order})
+                  {:on-drag-end (fn [new-values]
+                                  (when (seq new-values)
+                                    (swap! *property-schema assoc-in [:enum-config :values] new-values)
                                     (components-pu/update-property! property @*property-name @*property-schema)))}))
      (ui/dropdown
       (fn [{:keys [toggle-fn]}]
@@ -194,14 +189,8 @@
          "Add choice"])
       (fn [opts]
         (enum-new-item (assoc opts :on-save
-                              (fn [name description]
-                                (if (contains? (set (map :name (vals values))) name)
-                                  :value-exists
-                                  (let [id (random-uuid)
-                                        new-values (assoc values id {:name name
-                                                                     :description description})
-                                        new-order (vec (conj order id))]
-                                    (swap! *property-schema assoc :enum-config {:values new-values
-                                                                                :order new-order})
-                                    (components-pu/update-property! property @*property-name @*property-schema)))))))
+                              (fn [name icon description]
+                                (upsert-enum-item! property {:name name
+                                                             :description description
+                                                             :icon icon})))))
       dropdown-opts)]))

+ 20 - 16
src/main/frontend/components/property/value.cljs

@@ -330,13 +330,16 @@
         type (:type schema)
         enum? (= :enum type)
         items (if enum?
-                (map (fn [[id {:keys [name icon]}]]
-                       {:label (if icon
-                                 [:div.flex.flex-row.gap-2
-                                  (icon-component/icon icon)
-                                  name]
-                                 name)
-                        :value id}) (get-in schema [:enum-config :values]))
+                (keep (fn [id]
+                        (when-let [block (when id (db/entity [:block/uuid id]))]
+                          (let [icon (pu/get-property block :icon)
+                                name (:block/content block)]
+                            {:label (if icon
+                                      [:div.flex.flex-row.gap-2
+                                       (icon-component/icon icon)
+                                       name]
+                                      name)
+                             :value id}))) (get-in schema [:enum-config :values]))
                 (->> (model/get-block-property-values (:block/uuid property))
                      (mapcat (fn [[_id value]]
                                (if (coll? value)
@@ -369,8 +372,8 @@
                                             (exit-edit-property)
                                             (when-let [f (:on-chosen opts)] (f)))
                                  :on-click (fn []
-                                          (when *show-new-property-config?
-                                            (reset! *show-new-property-config? false)))
+                                             (when *show-new-property-config?
+                                               (reset! *show-new-property-config? false)))
                                  :on-key-down
                                  (fn [e]
                                    (case (util/ekey e)
@@ -433,7 +436,7 @@
       invalid-warning)))
 
 (rum/defc select-item
-  [property type value {:keys [page-cp inline-text]}]
+  [type value {:keys [page-cp inline-text]}]
   (case type
     (:page :date)
     (when-let [page (db/entity [:block/uuid value])]
@@ -444,10 +447,11 @@
     [:span.number (str value)]
 
     :enum
-    (let [value (get-in (:block/schema property) [:enum-config :values value])]
-      (if-let [icon (:icon value)]
-        (icon-component/icon icon)
-        (:name value)))
+    (when-let [block (when value (db/entity [:block/uuid value]))]
+      (let [name (:block/content block)]
+        (if-let [icon (pu/get-property block :icon)]
+          (icon-component/icon icon)
+          name)))
 
     (inline-text {} :markdown (str value))))
 
@@ -506,7 +510,7 @@
     (if (and select-type? (not= type :date))
       (single-value-select block property value
                            (fn []
-                             (select-item property type value opts))
+                             (select-item type value opts))
                            select-opts
                            opts)
       (case type
@@ -589,7 +593,7 @@
                     (if (seq items)
                       (concat
                        (for [item items]
-                         (select-item property type item opts))
+                         (select-item type item opts))
                        (when date?
                          [(date-picker block property nil {:toggle-fn toggle-fn})]))
                       (when-not editing? [:div.opacity-50.pointer.text-sm "Empty"])))

+ 1 - 1
src/main/frontend/db/rtc/const.cljs

@@ -65,7 +65,7 @@
 
 
 
-(def block-type-schema [:enum "property" "class" "whiteboard" "object" "hidden"])
+(def block-type-schema [:enum "property" "class" "whiteboard" "object" "hidden" "enum value"])
 (def op-schema
   [:multi {:dispatch first :decode/string #(update % 0 keyword)}
    [:move

+ 1 - 4
src/main/frontend/handler/db_based/property.cljs

@@ -223,10 +223,7 @@
                                       new-value)
                           block-properties (assoc properties property-uuid new-value)
                           refs (outliner-core/rebuild-block-refs block
-                                                                 block-properties
-                                                                 ;; enum values aren't actually blocks and shouldn't
-                                                                 ;; be referenced because they create garbage unknown blocks
-                                                                 :no-property-values? (= :enum property-type))]
+                                                                 block-properties)]
                       (db/transact! repo
                                     [[:db/retract (:db/id block) :block/refs]
                                      {:block/uuid (:block/uuid block)

+ 2 - 2
src/main/frontend/handler/plugin.cljs

@@ -468,7 +468,7 @@
             (state/set-state! :plugin/active-readme [content item])
             (state/set-sub-modal! (fn [_] (display))))
           (p/catch #(do (js/console.warn %)
-                        (notification/show! "No README content." :warn))))
+                        (notification/show! "No README content." :warning))))
       ;; market
       (state/set-sub-modal! (fn [_] (display repo nil))))))
 
@@ -803,4 +803,4 @@
    :auto-checking? (boolean (:plugin/updates-auto-checking? @state/state))
    :coming         (count (:plugin/updates-coming @state/state))
    :installing     (:plugin/installing @state/state)
-   :downloading?   (boolean (:plugin/updates-downloading? @state/state))})
+   :downloading?   (boolean (:plugin/updates-downloading? @state/state))})

+ 77 - 1
src/main/frontend/handler/property.cljs

@@ -3,6 +3,7 @@
   (:require [frontend.handler.db-based.property :as db-property-handler]
             [frontend.handler.file-based.property :as file-property-handler]
             [frontend.handler.file-based.page-property :as file-page-property]
+            [frontend.handler.notification :as notification]
             [frontend.config :as config]
             [frontend.util :as util]
             [frontend.state :as state]
@@ -10,7 +11,8 @@
             [frontend.format.block :as block]
             [frontend.db.model :as model]
             [frontend.modules.outliner.core :as outliner-core]
-            [frontend.handler.property.util :as pu]))
+            [frontend.handler.property.util :as pu]
+            [clojure.string :as string]))
 
 (defn remove-block-property!
   [repo block-id key]
@@ -238,6 +240,80 @@
     {:page page-tx
      :blocks [new-block]}))
 
+(defn upsert-enum-item
+  [property {:keys [id name icon description]}]
+  (when (= :enum (get-in property [:block/schema :type]))
+    (let [icon-id (pu/get-pid "icon")
+          name (string/trim name)
+          property-schema (:block/schema property)
+          enum-values (get-in property-schema [:enum-config :values])
+          block-values (map (fn [id] (db/entity [:block/uuid id])) enum-values)
+          icon (when-not (and (string? icon) (string/blank? icon)) icon)
+          description (string/trim description)
+          description (when-not (string/blank? description) description)]
+      (if (some (fn [b] (and (= name (:block/content b))
+                             (not= id (:block/uuid b)))) block-values)
+        (notification/show! "Choice exists already." :warning)
+        (let [block (when id (db/entity [:block/uuid id]))
+              block-id (or id (db/new-block-id))
+              tx-data (if block
+                        [(let [properties (:block/properties block)
+                               schema (:block/schema block)]
+                           {:block/uuid id
+                            :block/content name
+                            :block/properties (if icon
+                                                (assoc properties icon-id icon)
+                                                (dissoc properties icon-id))
+                            :block/schema (if description
+                                            (assoc schema :description description)
+                                            (dissoc schema :description))})]
+                        (let [page-name (str "$$$" (:block/uuid property))
+                              page-entity (db/entity [:block/name page-name])
+                              page (or page-entity
+                                       (-> (block/page-name->map page-name true)
+                                           (assoc :block/type #{"hidden"}
+                                                  :block/format :markdown)))
+                              page-tx (when-not page-entity page)
+                              page-id [:block/uuid (:block/uuid page)]
+                              metadata {:created-from-property (:block/uuid property)}
+                              new-block (cond->
+                                         {:block/type #{"enum value"}
+                                          :block/uuid block-id
+                                          :block/format :markdown
+                                          :block/content name
+                                          :block/page page-id
+                                          :block/metadata metadata
+                                          :block/parent page-id
+                                          :block/left (or (when page-entity (model/get-block-last-direct-child (db/get-db) (:db/id page-entity)))
+                                                          page-id)}
+                                          icon
+                                          (assoc :block/properties {icon-id icon})
+
+                                          description
+                                          (assoc :block/schema {:description description})
+
+                                          true
+                                          outliner-core/block-with-timestamps)
+                              new-values (vec (conj enum-values block-id))]
+                          (->> (cons page-tx [new-block
+                                              {:db/id (:db/id property)
+                                               :block/schema (assoc property-schema :enum-config {:values new-values})}])
+                               (remove nil?))))]
+          {:block-id block-id
+           :tx-data tx-data})))))
+
+(defn delete-enum-item
+  [property item]
+  (if (seq (:block/_refs item))
+    (notification/show! "The choice can't be deleted because it's still used." :warning)
+    (let [schema (:block/schema property)
+          tx-data [[:db/retractEntity (:db/id item)]
+                   {:db/id (:db/id property)
+                    :block/schema (update-in schema [:enum-config :values]
+                                             (fn [values]
+                                               (vec (remove #{(:block/uuid item)} values))))}]]
+      (db/transact! tx-data))))
+
 (defn get-property-block-created-block
   "Get the root block that created this property block."
   [eid]

+ 2 - 7
src/main/frontend/handler/property/util.cljs

@@ -75,11 +75,6 @@
         (swap! *db-built-in-properties assoc repo built-in-properties)))
     (set/subset? (set properties) (get @*db-built-in-properties repo))))
 
-(defn enum-value
-  "Given an enum ent and the value's uuid, return the value's string"
-  [ent value-uuid]
-  (get-in ent [:block/schema :enum-config :values value-uuid :name]))
-
 (defn readable-properties
   "Given a DB graph's properties, returns a readable properties map with keys as
   property names and property values dereferenced where possible. A property's
@@ -92,6 +87,6 @@
                      :block/name
                      keyword)
                  (if (= :enum (get-in prop-ent [:block/schema :type]))
-                   (enum-value prop-ent v)
+                   (:block/content (db/entity [:block/uuid v]))
                    v)])))
-       (into {})))
+       (into {})))

+ 2 - 2
src/main/frontend/modules/outliner/core.cljs

@@ -182,7 +182,7 @@
     (reset! (:editor/create-page? @state/state) false)))
 
 (defn rebuild-block-refs
-  [block new-properties & {:keys [skip-content-parsing? no-property-values?]}]
+  [block new-properties & {:keys [skip-content-parsing?]}]
   (let [property-key-refs (keys new-properties)
         property-value-refs (->> (vals new-properties)
                                  (mapcat (fn [v]
@@ -201,7 +201,7 @@
 
                                              :else
                                              nil))))
-        property-refs (->> (concat property-key-refs (when-not no-property-values? property-value-refs))
+        property-refs (->> (concat property-key-refs property-value-refs)
                            (map (fn [id-or-map] (if (uuid? id-or-map) {:block/uuid id-or-map} id-or-map))))
         content-refs (when-not skip-content-parsing?
                        (some-> (:block/content block) block/extract-refs-from-text))]