diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index 3ba7aa5af..aa7658b17 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -12,10 +12,9 @@ [app.common.time :as dt] [app.common.transit :as t] [app.common.types.token :as cto] + [cuerdas.core :as str] #?(:clj [app.common.fressian :as fres]))) -;; #?(:clj (set! *warn-on-reflection* true)) - ;; === Token (defrecord Token [name type value description modified-at]) @@ -56,7 +55,8 @@ (defprotocol ITokenSet (add-token [_ token] "add a token at the end of the list") (update-token [_ token-name f] "update a token in the list") - (delete-token [_ token-name] "delete a token from the list")) + (delete-token [_ token-name] "delete a token from the list") + (get-tokens [_] "return an ordered sequence of all tokens in the set")) (defrecord TokenSet [name description modified-at tokens] ITokenSet @@ -87,7 +87,10 @@ (TokenSet. name description (dt/now) - (dissoc tokens token-name)))) + (dissoc tokens token-name))) + + (get-tokens [_] + (vals tokens))) (def schema:token-set [:and [:map {:title "TokenSet"} @@ -442,3 +445,90 @@ (let [sets (fres/read-object! r) themes (fres/read-object! r)] (->TokensLib sets themes)))})) + +;; === Groups handling + +(def schema:groupable-item + [:map {:title "Groupable item"} + [:name :string]]) + +(def valid-groupable-item? + (sm/validator schema:groupable-item)) + +(defn split-path + "Decompose a string in the form 'one.two.three' into a vector of strings, removing spaces." + [path] + (let [xf (comp (map str/trim) + (remove str/empty?))] + (->> (str/split path ".") + (into [] xf)))) + +(defn join-path + "Regenerate a path as a string, from a vector." + [path-vec] + (str/join "." path-vec)) + +(defn group-item + "Add a group to the item name, in the form group.name." + [item group-name] + (dm/assert! + "expected groupable item" + (valid-groupable-item? item)) + (update item :name #(str group-name "." %))) + +(defn ungroup-item + "Remove the first group from the item name." + [item] + (dm/assert! + "expected groupable item" + (valid-groupable-item? item)) + (update item :name #(-> % + (split-path) + (rest) + (join-path)))) + +(defn get-path + "Get the groups part of the name. E.g. group.subgroup.name -> group.subrgoup" + [item] + (dm/assert! + "expected groupable item" + (valid-groupable-item? item)) + (-> (:name item) + (split-path) + (butlast) + (join-path))) + +(defn get-final-name + "Get the final part of the name. E.g. group.subgroup.name -> name" + [item] + (dm/assert! + "expected groupable item" + (valid-groupable-item? item)) + (-> (:name item) + (split-path) + (last))) + +(defn group-items + "Convert a sequence of items in a nested structure like this: + + {'': [itemA itemB] + 'group1': {'': [item1A item1B] + 'subgroup11': {'': [item11A item11B item11C]} + 'subgroup12': {'': [item12A]}} + 'group2': {'subgroup21': {'': [item21A]}}} + " + [items & {:keys [reverse-sort?]}] + (when (seq items) + (reduce (fn [groups item] + (let [pathv (split-path (:name item))] + (update-in groups + pathv + (fn [group] + (if group + (cons group item) + item))))) + (sorted-map-by (fn [key1 key2] ;; TODO: this does not work well with ordered-map + (if reverse-sort? ;; we need to think a bit more of this + (compare key2 key1) + (compare key1 key2)))) + items))) diff --git a/common/test/common_tests/types/tokens_lib_test.cljc b/common/test/common_tests/types/tokens_lib_test.cljc index 3c24353da..6d8d060f7 100644 --- a/common/test/common_tests/types/tokens_lib_test.cljc +++ b/common/test/common_tests/types/tokens_lib_test.cljc @@ -409,3 +409,104 @@ (t/is (ctob/valid-tokens-lib? tokens-lib')) (t/is (= (ctob/set-count tokens-lib') 1)) (t/is (= (ctob/theme-count tokens-lib') 1))))) + +(t/testing "grouping" + (t/deftest split-and-join + (let [name "group.subgroup.name" + path (ctob/split-path name) + name' (ctob/join-path path)] + (t/is (= (first path) "group")) + (t/is (= (second path) "subgroup")) + (t/is (= (nth path 2) "name")) + (t/is (= name' name)))) + + (t/deftest remove-spaces + (let [name "group . subgroup . name" + path (ctob/split-path name)] + (t/is (= (first path) "group")) + (t/is (= (second path) "subgroup")) + (t/is (= (nth path 2) "name")))) + + (t/deftest group-and-ungroup + (let [token1 (ctob/make-token :name "token1" :type :boolean :value true) + token2 (ctob/make-token :name "some group.token2" :type :boolean :value true) + + token1' (ctob/group-item token1 "big group") + token2' (ctob/group-item token2 "big group") + token1'' (ctob/ungroup-item token1') + token2'' (ctob/ungroup-item token2')] + (t/is (= (:name token1') "big group.token1")) + (t/is (= (:name token2') "big group.some group.token2")) + (t/is (= (:name token1'') "token1")) + (t/is (= (:name token2'') "some group.token2")))) + + (t/deftest get-path + (let [token1 (ctob/make-token :name "token1" :type :boolean :value true) + token2 (ctob/make-token :name "some-group.token2" :type :boolean :value true) + token3 (ctob/make-token :name "some-group.some-subgroup.token3" :type :boolean :value true)] + (t/is (= (ctob/get-path token1) "")) + (t/is (= (ctob/get-path token2) "some-group")) + (t/is (= (ctob/get-path token3) "some-group.some-subgroup")))) + + (t/deftest get-final-name + (let [token1 (ctob/make-token :name "token1" :type :boolean :value true) + token2 (ctob/make-token :name "some-group.token2" :type :boolean :value true) + token3 (ctob/make-token :name "some-group.some-subgroup.token3" :type :boolean :value true)] + (t/is (= (ctob/get-final-name token1) "token1")) + (t/is (= (ctob/get-final-name token2) "token2")) + (t/is (= (ctob/get-final-name token3) "token3")))) + + (t/deftest group-items + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "token-set1")) + (ctob/add-set (ctob/make-token-set :name "sgroup1.token-set2")) + (ctob/add-set (ctob/make-token-set :name "sgroup1.token-set3")) + (ctob/add-set (ctob/make-token-set :name "sgroup1.ssubgroup1.token-set4")) + (ctob/add-set (ctob/make-token-set :name "sgroup2.token-set5")) + (ctob/add-token-in-set "sgroup2.token-set5" + (ctob/make-token :name "tgroup1.tsubgroup1.token1" + :type :boolean + :value true)) + (ctob/add-token-in-set "sgroup2.token-set5" + (ctob/make-token :name "tgroup1.tsubgroup1.token2" + :type :boolean + :value true))) + sets (ctob/get-sets tokens-lib) + set (ctob/get-set tokens-lib "sgroup2.token-set5") + tokens (ctob/get-tokens set) + + set-groups (ctob/group-items sets) + + token-set1 (get set-groups "token-set1") + sgroup1 (get set-groups "sgroup1") + token-set2 (get sgroup1 "token-set2") + token-set3 (get sgroup1 "token-set3") + ssubgroup1 (get sgroup1 "ssubgroup1") + token-set4 (get ssubgroup1 "token-set4") + sgroup2 (get set-groups "sgroup2") + token-set5 (get sgroup2 "token-set5") + + + token-groups (ctob/group-items tokens) + tgroup1 (get token-groups "tgroup1") + tsubgroup1 (get tgroup1 "tsubgroup1") + token1 (get tsubgroup1 "token1") + token2 (get tsubgroup1 "token2")] + + ;; {"sgroup1" + ;; {"token-set2" {:name "sgroup1.token-set2" ...} + ;; "token-set3" {:name "sgroup1.token-set3" ...} + ;; "ssubgroup1" + ;; {"token-set4" {:name "sgroup1.ssubgroup1.token-set4" ...}}} + ;; "sgroup2" + ;; {"token-set5" {:name "sgroup2.token-set5" ...}} + ;; "token-set1" {:name "token-set1" ...}} + + (t/is (= (:name token-set1) "token-set1")) + (t/is (= (:name token-set2) "sgroup1.token-set2")) + (t/is (= (:name token-set3) "sgroup1.token-set3")) + (t/is (= (:name token-set4) "sgroup1.ssubgroup1.token-set4")) + (t/is (= (:name token-set5) "sgroup2.token-set5")) + + (t/is (= (:name token1) "tgroup1.tsubgroup1.token1")) + (t/is (= (:name token2) "tgroup1.tsubgroup1.token2"))))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/groups.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/groups.cljs index 82d97180d..b8fa39058 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/groups.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/groups.cljs @@ -69,11 +69,11 @@ (defn group-assets "Convert a list of assets in a nested structure like this: - {'': [{assetA} {assetB}] - 'group1': {'': [{asset1A} {asset1B}] - 'subgroup11': {'': [{asset11A} {asset11B} {asset11C}]} - 'subgroup12': {'': [{asset12A}]}} - 'group2': {'subgroup21': {'': [{asset21A}}}} + {'': [assetA assetB] + 'group1': {'': [asset1A asset1B] + 'subgroup11': {'': [asset11A asset11B asset11C]} + 'subgroup12': {'': [asset12A]}} + 'group2': {'subgroup21': {'': [asset21A]}}} " [assets reverse-sort?] (when-not (empty? assets)