diff --git a/common/src/app/common/files/changes.cljc b/common/src/app/common/files/changes.cljc index 78845e8f4f..7a0207c795 100644 --- a/common/src/app/common/files/changes.cljc +++ b/common/src/app/common/files/changes.cljc @@ -31,6 +31,7 @@ [app.common.types.tokens-lib :as ctob] [app.common.types.typographies-list :as ctyl] [app.common.types.typography :as ctt] + [app.common.types.variant :as ctv] [app.common.uuid :as uuid] [clojure.set :as set])) @@ -336,7 +337,9 @@ [:type [:= :mod-component]] [:id ::sm/uuid] [:shapes {:optional true} [:vector {:gen/max 3} :any]] - [:name {:optional true} :string]]] + [:name {:optional true} :string] + [:variant-id {:optional true} ::sm/uuid] + [:variant-properties {:optional true} [:vector ::ctv/variant-property]]]] [:del-component [:map {:title "DelComponentChange"} diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index aa360d2926..4e4836b6c2 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -964,11 +964,13 @@ :name name :main-instance-id main-instance-id :main-instance-page main-instance-page - :annotation annotation - :variant-id variant-id - :variant-properties variant-properties} + :annotation annotation} (some? new-shapes) ;; this will be null in components-v2 - (assoc :shapes (vec new-shapes)))) + (assoc :shapes (vec new-shapes)) + (some? variant-id) + (assoc :variant-id variant-id) + (seq variant-properties) + (assoc :variant-properties variant-properties))) (into (map mk-change) updated-shapes)))) (update :undo-changes (fn [undo-changes] @@ -991,27 +993,39 @@ new-component (update-fn prev-component)] (if prev-component (-> changes - (update :redo-changes conj {:type :mod-component - :id id - :name (:name new-component) - :path (:path new-component) - :main-instance-id (:main-instance-id new-component) - :main-instance-page (:main-instance-page new-component) - :annotation (:annotation new-component) - :variant-id (:variant-id new-component) - :variant-properties (:variant-properties new-component) - :objects (:objects new-component) ;; this won't exist in components-v2 (except for deleted components) - :modified-at (:modified-at new-component)}) - (update :undo-changes conj {:type :mod-component - :id id - :name (:name prev-component) - :path (:path prev-component) - :main-instance-id (:main-instance-id prev-component) - :main-instance-page (:main-instance-page prev-component) - :annotation (:annotation prev-component) - :variant-id (:variant-id prev-component) - :variant-properties (:variant-properties prev-component) - :objects (:objects prev-component)}) + (update :redo-changes conj (cond-> {:type :mod-component + :id id + :name (:name new-component) + :path (:path new-component) + :main-instance-id (:main-instance-id new-component) + :main-instance-page (:main-instance-page new-component) + :annotation (:annotation new-component) + :objects (:objects new-component) ;; this won't exist in components-v2 (except for deleted components) + :modified-at (:modified-at new-component)} + (some? (:variant-id new-component)) + (assoc :variant-id (:variant-id new-component)) + (nil? (:variant-id new-component)) + (dissoc :variant-id) + (seq (:variant-properties new-component)) + (assoc :variant-properties (:variant-properties new-component)) + (not (seq (:variant-properties new-component))) + (dissoc :variant-properties))) + (update :undo-changes conj (cond-> {:type :mod-component + :id id + :name (:name prev-component) + :path (:path prev-component) + :main-instance-id (:main-instance-id prev-component) + :main-instance-page (:main-instance-page prev-component) + :annotation (:annotation prev-component) + :objects (:objects prev-component)} + (some? (:variant-id prev-component)) + (assoc :variant-id (:variant-id prev-component)) + (nil? (:variant-id prev-component)) + (dissoc :variant-id) + (seq (:variant-properties prev-component)) + (assoc :variant-properties (:variant-properties prev-component)) + (not (seq (:variant-properties prev-component))) + (dissoc :variant-properties))) (cond-> apply-changes-local-library? (apply-changes-local {:apply-to-library? true}))) changes))) diff --git a/common/src/app/common/files/repair.cljc b/common/src/app/common/files/repair.cljc index 66fd58c713..a99307ee17 100644 --- a/common/src/app/common/files/repair.cljc +++ b/common/src/app/common/files/repair.cljc @@ -572,6 +572,41 @@ (pcb/with-file-data file-data) (pcb/update-shapes [(:id shape)] repair-shape)))) +(defmethod repair-error :not-a-variant + [_ error file _] + (log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error)) + file) + +(defmethod repair-error :invalid-variant-id + [_ error file _] + (log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error)) + file) + +(defmethod repair-error :invalid-variant-properties + [_ error file _] + (log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error)) + file) + +(defmethod repair-error :variant-not-main + [_ error file _] + (log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error)) + file) + +(defmethod repair-error :parent-not-variant + [_ error file _] + (log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error)) + file) + +(defmethod repair-error :variant-bad-name + [_ error file _] + (log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error)) + file) + +(defmethod repair-error :variant-no-properties + [_ error file _] + (log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error)) + file) + (defmethod repair-error :default [_ error file _] (log/error :hint "Unknown error code, don't know how to repair" :code (:code error)) diff --git a/common/src/app/common/files/validate.cljc b/common/src/app/common/files/validate.cljc index 8c3746c310..ea7777cdd2 100644 --- a/common/src/app/common/files/validate.cljc +++ b/common/src/app/common/files/validate.cljc @@ -10,12 +10,15 @@ [app.common.data.macros :as dm] [app.common.exceptions :as ex] [app.common.files.helpers :as cfh] + [app.common.files.variant :as cfv] [app.common.schema :as sm] [app.common.types.component :as ctk] + [app.common.types.components-list :as ctkl] [app.common.types.container :as ctn] [app.common.types.file :as ctf] [app.common.types.pages-list :as ctpl] [app.common.types.shape-tree :as ctst] + [app.common.types.variant :as ctv] [app.common.uuid :as uuid] [cuerdas.core :as str])) @@ -56,7 +59,14 @@ :instance-head-not-frame :misplaced-slot :missing-slot - :shape-ref-cycle}) + :shape-ref-cycle + :not-a-variant + :invalid-variant-id + :invalid-variant-properties + :variant-not-main + :parent-not-variant + :variant-bad-name + :variant-no-properties}) (def ^:private schema:error [:map {:title "ValidationError"} @@ -401,6 +411,53 @@ (check-empty-swap-slot shape file page) (run! #(check-shape % file page libraries :context :not-component) (:shapes shape))) +(defn- check-variant-container + "Shape is a variant container, so: + -all its children should be variants with variant-id equals to the shape-id + -all the components should have the same properties" + [shape file page] + (let [shape-id (:id shape) + shapes (:shapes shape) + children (map #(ctst/get-shape page %) shapes) + prop-names (cfv/extract-properties-names (first children) (:data file))] + (doseq [child children] + (if (not (ctk/is-variant? child)) + (report-error :not-a-variant + (str/ffmt "Shape % should be a variant" (:id child)) + child file page) + (do + (when (not= (:variant-id child) shape-id) + (report-error :invalid-variant-id + (str/ffmt "Variant % has invalid variant-id %" (:id child) (:variant-id child)) + child file page)) + (when (not= prop-names (cfv/extract-properties-names child (:data file))) + (report-error :invalid-variant-properties + (str/ffmt "Variant % has invalid properties" (:id child)) + child file page))))))) + +(defn- check-variant + "Shape is a variant, so + -it should be a main component + -its parent should be a variant-container + -its variant-name is derived from the properties" + [shape file page] + (let [parent (ctst/get-shape page (:parent-id shape)) + component (ctkl/get-component (:data file) (:component-id shape) true) + name (ctv/properties-to-name (:variant-properties component))] + (when-not (ctk/main-instance? shape) + (report-error :variant-not-main + (str/ffmt "Variant % is not a main instance" (:id shape)) + shape file page)) + (when-not (ctk/is-variant-container? parent) + (report-error :parent-not-variant + (str/ffmt "Variant % has an invalid parent" (:id shape)) + shape file page)) + + (when-not (= name (:variant-name shape)) + (report-error :variant-bad-name + (str/ffmt "Variant % has an invalid variant-name" (:id shape)) + shape file page)))) + (defn- check-shape "Validate referential integrity and semantic coherence of a shape and all its children. Report all errors found. @@ -421,6 +478,12 @@ (check-parent-children shape file page) (check-frame shape file page) + (when (ctk/is-variant-container? shape) + (check-variant-container shape file page)) + + (when (ctk/is-variant? shape) + (check-variant shape file page)) + (if (ctk/instance-head? shape) (if (not= :frame (:type shape)) (report-error :instance-head-not-frame @@ -496,6 +559,22 @@ "This deleted component has shapes with shape-ref pointing to self" component file nil :cycles-ids cycles-ids)))) +(defn- check-variant-component + "Component is a variant, so: + -Its main should be a variant + -It should have at least one variant property" + [component file] + (let [component-page (ctf/get-component-page (:data file) component) + main-component (ctst/get-shape component-page (:main-instance-id component))] + (when-not (ctk/is-variant? main-component) + (report-error :not-a-variant + (str/ffmt "Shape % should be a variant" (:id main-component)) + main-component file component-page)) + (when (< (count (:variant-properties component)) 1) + (report-error :variant-no-properties + (str/ffmt "Component variant % should have properties" (:id main-component)) + main-component file nil)))) + (defn- check-component "Validate semantic coherence of a component. Report all errors found." [component file] @@ -505,7 +584,10 @@ component file nil)) (when (:deleted component) (check-component-duplicate-swap-slot component file) - (check-ref-cycles component file))) + (check-ref-cycles component file)) + + (when (ctk/is-variant? component) + (check-variant-component component file))) (defn- get-orphan-shapes [{:keys [objects] :as page}] diff --git a/common/src/app/common/files/variant.cljc b/common/src/app/common/files/variant.cljc new file mode 100644 index 0000000000..226929768f --- /dev/null +++ b/common/src/app/common/files/variant.cljc @@ -0,0 +1,63 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC +(ns app.common.files.variant + (:require + [app.common.data.macros :as dm] + [app.common.types.components-list :as ctcl] + [app.common.types.variant :as ctv] + [cuerdas.core :as str])) + + +(defn find-variant-components + "Find a list of the components thet belongs to this variant-id" + [data objects variant-id] + (->> (dm/get-in objects [variant-id :shapes]) + (map #(dm/get-in objects [% :component-id])) + (map #(ctcl/get-component data % true)) + reverse)) + +(defn- dashes-to-end + [property-values] + (let [dashes (if (some #(= % "--") property-values) ["--"] [])] + (concat (remove #(= % "--") property-values) dashes))) + + +(defn extract-properties-names + [shape data] + (->> shape + (#(ctcl/get-component data (:component-id %) true)) + :variant-properties + (map :name))) + + +(defn extract-properties-values + [data objects variant-id] + (->> (find-variant-components data objects variant-id) + (mapcat :variant-properties) + (group-by :name) + (map (fn [[k v]] + {:name k + :value (->> v + (map #(if (str/empty? (:value %)) "--" (:value %))) + distinct + dashes-to-end)})))) + +(defn get-variant-mains + [component data] + (assert (ctv/valid-variant-component? component) "expected valid component variant") + (when-let [variant-id (:variant-id component)] + (let [page-id (:main-instance-page component) + objects (-> (dm/get-in data [:pages-index page-id]) + (get :objects))] + (dm/get-in objects [variant-id :shapes])))) + + +(defn is-secondary-variant? + [component data] + (let [shapes (get-variant-mains component data)] + (and (seq shapes) + (not= (:main-instance-id component) (last shapes))))) + diff --git a/common/src/app/common/logic/shapes.cljc b/common/src/app/common/logic/shapes.cljc index fa2ba8e5e7..94faa7b82f 100644 --- a/common/src/app/common/logic/shapes.cljc +++ b/common/src/app/common/logic/shapes.cljc @@ -10,12 +10,13 @@ [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] [app.common.geom.shapes :as gsh] - [app.common.logic.variants :as clv] + [app.common.logic.variant-properties :as clvp] [app.common.types.component :as ctk] [app.common.types.container :as ctn] [app.common.types.shape.interactions :as ctsi] [app.common.types.shape.layout :as ctl] [app.common.types.token :as cto] + [app.common.types.variant :as ctv] [app.common.uuid :as uuid] [cuerdas.core :as str])) @@ -413,7 +414,7 @@ (- max-path-items num-props)) changes (nth - (iterate #(clv/generate-add-new-property % (:id parent)) changes) + (iterate #(clvp/generate-add-new-property % (:id parent)) changes) num-new-props)] (reduce (fn [changes shape] @@ -424,11 +425,11 @@ ;; we need to get the updated library data to have access to the current properties data (pcb/get-library-data changes) - props (clv/path-to-properties + props (ctv/path-to-properties base-name (get-in data [:components first-comp-id :variant-properties])) - variant-name (clv/properties-to-name props) + variant-name (ctv/properties-to-name props) [cpath cname] (cfh/parse-path-name (:name parent))] (-> (pcb/update-component changes diff --git a/common/src/app/common/logic/variant_properties.cljc b/common/src/app/common/logic/variant_properties.cljc new file mode 100644 index 0000000000..a0224dbe4f --- /dev/null +++ b/common/src/app/common/logic/variant_properties.cljc @@ -0,0 +1,93 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC +(ns app.common.logic.variant-properties + (:require + [app.common.data :as d] + [app.common.files.changes-builder :as pcb] + [app.common.files.variant :as cfv] + [app.common.types.components-list :as ctcl] + [app.common.types.variant :as ctv] + [cuerdas.core :as str])) + +(defn generate-update-property-name + [changes variant-id pos new-name] + (let [data (pcb/get-library-data changes) + objects (pcb/get-objects changes) + related-components (cfv/find-variant-components data objects variant-id)] + (reduce (fn [changes component] + (pcb/update-component + changes (:id component) + #(assoc-in % [:variant-properties pos :name] new-name) + {:apply-changes-local-library? true})) + changes + related-components))) + + +(defn generate-remove-property + [changes variant-id pos] + (let [data (pcb/get-library-data changes) + objects (pcb/get-objects changes) + related-components (cfv/find-variant-components data objects variant-id)] + (reduce (fn [changes component] + (let [props (:variant-properties component) + props (d/remove-at-index props pos) + main-id (:main-instance-id component) + name (ctv/properties-to-name props)] + (-> changes + (pcb/update-component (:id component) #(assoc % :variant-properties props) + {:apply-changes-local-library? true}) + (pcb/update-shapes [main-id] #(assoc % :variant-name name))))) + changes + related-components))) + + +(defn generate-update-property-value + [changes component-id pos value] + (let [data (pcb/get-library-data changes) + component (ctcl/get-component data component-id true) + main-id (:main-instance-id component) + name (-> (:variant-properties component) + (update pos assoc :value value) + ctv/properties-to-name)] + (-> changes + (pcb/update-component component-id #(assoc-in % [:variant-properties pos :value] value) + {:apply-changes-local-library? true}) + (pcb/update-shapes [main-id] #(assoc % :variant-name name))))) + + +(defn generate-add-new-property + [changes variant-id & {:keys [fill-values?]}] + (let [data (pcb/get-library-data changes) + objects (pcb/get-objects changes) + related-components (cfv/find-variant-components data objects variant-id) + + props (-> related-components first :variant-properties) + next-prop-num (ctv/next-property-number props) + property-name (str ctv/property-prefix next-prop-num) + + [_ changes] + (reduce (fn [[num changes] component] + (let [main-id (:main-instance-id component) + + update-props #(-> (d/nilv % []) + (conj {:name property-name + :value (if fill-values? (str ctv/value-prefix num) "")})) + + update-name #(if fill-values? + (if (str/empty? %) + (str ctv/value-prefix num) + (str % ", " ctv/value-prefix num)) + %)] + [(inc num) + (-> changes + (pcb/update-component (:id component) + #(update % :variant-properties update-props) + {:apply-changes-local-library? true}) + (pcb/update-shapes [main-id] #(update % :variant-name update-name)))])) + [1 changes] + related-components)] + changes)) + diff --git a/common/src/app/common/logic/variants.cljc b/common/src/app/common/logic/variants.cljc index c624c757bc..7b30e08525 100644 --- a/common/src/app/common/logic/variants.cljc +++ b/common/src/app/common/logic/variants.cljc @@ -1,160 +1,31 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS INC (ns app.common.logic.variants (:require - [app.common.data :as d] - [app.common.data.macros :as dm] [app.common.files.changes-builder :as pcb] - [app.common.files.helpers :as cfh] - [app.common.types.components-list :as ctcl] - [cuerdas.core :as str])) + [app.common.files.variant :as cfv] + [app.common.logic.libraries :as cll] + [app.common.logic.variant-properties :as clvp] + [app.common.types.variant :as ctv])) +(defn generate-add-new-variant + [changes shape variant-id new-component-id new-shape-id prop-num] + (let [data (pcb/get-library-data changes) + objects (pcb/get-objects changes) + component-id (:component-id shape) + value (str ctv/value-prefix + (-> (cfv/extract-properties-values data objects variant-id) + last + :value + count + inc)) -(def property-prefix "Property") -(def property-regex (re-pattern (str property-prefix "(\\d+)"))) -(def value-prefix "Value") - -(defn find-related-components - "Find a list of the components thet belongs to this variant-id" - [data objects variant-id] - (->> (dm/get-in objects [variant-id :shapes]) - (map #(dm/get-in objects [% :component-id])) - (map #(ctcl/get-component data % true)) - reverse)) - - -(defn properties-to-name - "Transform the properties into a name, with the values separated by comma" - [properties] - (->> properties - (map :value) - (remove str/empty?) - (str/join ", "))) - - -(defn next-property-number - "Returns the next property number, to avoid duplicates on the property names" - [properties] - (let [numbers (keep - #(some->> (:name %) (re-find property-regex) second d/parse-integer) - properties) - max-num (if (seq numbers) - (apply max numbers) - 0)] - (inc (max max-num (count properties))))) - - -(defn path-to-properties - "From a list of properties and a name with path, assign each token of the - path as value of a different property" - [path properties] - (let [next-prop-num (next-property-number properties) - cpath (cfh/split-path path) - assigned (mapv #(assoc % :value (nth cpath %2 "")) properties (range)) - remaining (drop (count properties) cpath) - new-properties (map-indexed (fn [i v] {:name (str property-prefix (+ next-prop-num i)) - :value v}) remaining)] - (into assigned new-properties))) - -(defn- dashes-to-end - [property-values] - (let [dashes (if (some #(= % "--") property-values) ["--"] [])] - (concat (remove #(= % "--") property-values) dashes))) - - -(defn extract-properties-values - [data objects variant-id] - (->> (find-related-components data objects variant-id) - (mapcat :variant-properties) - (group-by :name) - (map (fn [[k v]] - {:name k - :value (->> v - (map #(if (str/empty? (:value %)) "--" (:value %))) - distinct - dashes-to-end)})))) - - -(defn generate-update-property-name - [changes variant-id pos new-name] - (let [data (pcb/get-library-data changes) - objects (pcb/get-objects changes) - related-components (find-related-components data objects variant-id)] - (reduce (fn [changes component] - (pcb/update-component - changes (:id component) - #(assoc-in % [:variant-properties pos :name] new-name) - {:apply-changes-local-library? true})) - changes - related-components))) - - -(defn generate-remove-property - [changes variant-id pos] - (let [data (pcb/get-library-data changes) - objects (pcb/get-objects changes) - related-components (find-related-components data objects variant-id)] - (reduce (fn [changes component] - (let [props (:variant-properties component) - props (d/remove-at-index props pos) - main-id (:main-instance-id component) - name (properties-to-name props)] - (-> changes - (pcb/update-component (:id component) #(assoc % :variant-properties props) - {:apply-changes-local-library? true}) - (pcb/update-shapes [main-id] #(assoc % :variant-name name))))) - changes - related-components))) - - -(defn generate-update-property-value - [changes component-id pos value] - (let [data (pcb/get-library-data changes) - component (ctcl/get-component data component-id true) - main-id (:main-instance-id component) - name (-> (:variant-properties component) - (update pos assoc :value value) - properties-to-name)] + [new-shape changes] (-> changes + (cll/generate-duplicate-component + {:data data} + component-id + new-component-id + true + {:new-shape-id new-shape-id :apply-changes-local-library? true}))] (-> changes - (pcb/update-component component-id #(assoc-in % [:variant-properties pos :value] value) - {:apply-changes-local-library? true}) - (pcb/update-shapes [main-id] #(assoc % :variant-name name))))) - - -(defn generate-add-new-property - [changes variant-id & {:keys [fill-values?]}] - (let [data (pcb/get-library-data changes) - objects (pcb/get-objects changes) - related-components (find-related-components data objects variant-id) - - props (-> related-components first :variant-properties) - next-prop-num (next-property-number props) - property-name (str property-prefix next-prop-num) - - [_ changes] - (reduce (fn [[num changes] component] - (let [main-id (:main-instance-id component) - - update-props #(-> (d/nilv % []) - (conj {:name property-name - :value (if fill-values? (str value-prefix num) "")})) - - update-name #(if fill-values? - (if (str/empty? %) - (str value-prefix num) - (str % ", " value-prefix num)) - %)] - [(inc num) - (-> changes - (pcb/update-component (:id component) - #(update % :variant-properties update-props) - {:apply-changes-local-library? true}) - (pcb/update-shapes [main-id] #(update % :variant-name update-name)))])) - [1 changes] - related-components)] - changes)) - + (clvp/generate-update-property-value new-component-id prop-num value) + (pcb/change-parent (:parent-id shape) [new-shape] 0)))) diff --git a/common/src/app/common/test_helpers/variants.cljc b/common/src/app/common/test_helpers/variants.cljc index 804dd3c7e7..e9a76d0628 100644 --- a/common/src/app/common/test_helpers/variants.cljc +++ b/common/src/app/common/test_helpers/variants.cljc @@ -22,4 +22,18 @@ (thc/make-component component1-label root1-label) (thc/update-component component1-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "Value1"}]}) (thc/make-component component2-label root2-label) - (thc/update-component component2-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "Value1"}]})))) + (thc/update-component component2-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "Value2"}]})))) + +(defn add-variant-two-properties + [file variant-label component1-label root1-label component2-label root2-label + & {:keys []}] + (let [file (ths/add-sample-shape file variant-label :type :frame :is-variant-container true) + variant-id (thi/id variant-label)] + + (-> file + (ths/add-sample-shape root2-label :type :frame :parent-label variant-label :variant-id variant-id :variant-name "p1v2, p2v2") + (ths/add-sample-shape root1-label :type :frame :parent-label variant-label :variant-id variant-id :variant-name "p1v1, p2v1") + (thc/make-component component1-label root1-label) + (thc/update-component component1-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "p1v1"} {:name "Property2" :value "p2v1"}]}) + (thc/make-component component2-label root2-label) + (thc/update-component component2-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "p1v2"} {:name "Property2" :value "p2v2"}]})))) diff --git a/common/src/app/common/types/component.cljc b/common/src/app/common/types/component.cljc index 14844153e9..dc5c85558e 100644 --- a/common/src/app/common/types/component.cljc +++ b/common/src/app/common/types/component.cljc @@ -10,6 +10,7 @@ [app.common.schema :as sm] [app.common.types.page :as ctp] [app.common.types.plugins :as ctpg] + [app.common.types.variant :as ctv] [cuerdas.core :as str])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -17,15 +18,17 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def schema:component - [:map - [:id ::sm/uuid] - [:name :string] - [:path {:optional true} [:maybe :string]] - [:modified-at {:optional true} ::sm/inst] - [:objects {:gen/max 10 :optional true} ::ctp/objects] - [:main-instance-id ::sm/uuid] - [:main-instance-page ::sm/uuid] - [:plugin-data {:optional true} ::ctpg/plugin-data]]) + [:merge + [:map + [:id ::sm/uuid] + [:name :string] + [:path {:optional true} [:maybe :string]] + [:modified-at {:optional true} ::sm/inst] + [:objects {:gen/max 10 :optional true} ::ctp/objects] + [:main-instance-id ::sm/uuid] + [:main-instance-page ::sm/uuid] + [:plugin-data {:optional true} ::ctpg/plugin-data]] + ::ctv/variant-component]) (sm/register! ::component schema:component) diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index 5033965dba..0f11a1805e 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -32,6 +32,7 @@ [app.common.types.shape.shadow :as ctss] [app.common.types.shape.text :as ctsx] [app.common.types.token :as cto] + [app.common.types.variant :as ctv] [app.common.uuid :as uuid] [clojure.set :as set])) @@ -317,7 +318,9 @@ schema:frame-attrs schema:shape-attrs schema:shape-geom-attrs - schema:shape-base-attrs]] + schema:shape-base-attrs + ::ctv/variant-shape + ::ctv/variant-container]] [:bool [:merge {:title "BoolShape"} diff --git a/common/src/app/common/types/variant.cljc b/common/src/app/common/types/variant.cljc new file mode 100644 index 0000000000..9f5eded0a6 --- /dev/null +++ b/common/src/app/common/types/variant.cljc @@ -0,0 +1,88 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.common.types.variant + (:require + [app.common.data :as d] + [app.common.files.helpers :as cfh] + [app.common.schema :as sm] + [cuerdas.core :as str])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; SCHEMA +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def schema:variant-property + [:map + [:name :string] + [:value :string]]) + +(def schema:variant-component + ;; A component that is part of a variant set. + [:map + [:variant-id {:optional true} ::sm/uuid] + [:variant-properties {:optional true} [:vector schema:variant-property]]]) + +(def schema:variant-shape + ;; The root shape of the main instance of a variant component. + [:map + [:variant-id {:optional true} ::sm/uuid] + [:variant-name {:optional true} :string]]) + +(def schema:variant-container + ;; is a board that contains all variant components of a variant set, + ;; for grouping them visually in the workspace. + [:map + [:is-variant-container {:optional true} :boolean]]) + +(sm/register! ::variant-property schema:variant-property) +(sm/register! ::variant-component schema:variant-component) +(sm/register! ::variant-shape schema:variant-shape) +(sm/register! ::variant-container schema:variant-container) + +(def valid-variant-component? + (sm/check-fn schema:variant-component)) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def property-prefix "Property") +(def property-regex (re-pattern (str property-prefix "(\\d+)"))) +(def value-prefix "Value") + + +(defn properties-to-name + "Transform the properties into a name, with the values separated by comma" + [properties] + (->> properties + (map :value) + (remove str/empty?) + (str/join ", "))) + + +(defn next-property-number + "Returns the next property number, to avoid duplicates on the property names" + [properties] + (let [numbers (keep + #(some->> (:name %) (re-find property-regex) second d/parse-integer) + properties) + max-num (if (seq numbers) + (apply max numbers) + 0)] + (inc (max max-num (count properties))))) + + +(defn path-to-properties + "From a list of properties and a name with path, assign each token of the + path as value of a different property" + [path properties] + (let [next-prop-num (next-property-number properties) + cpath (cfh/split-path path) + assigned (mapv #(assoc % :value (nth cpath %2 "")) properties (range)) + remaining (drop (count properties) cpath) + new-properties (map-indexed (fn [i v] {:name (str property-prefix (+ next-prop-num i)) + :value v}) remaining)] + (into assigned new-properties))) diff --git a/common/test/common_tests/logic/variants_test.cljc b/common/test/common_tests/logic/variants_test.cljc index e37229d263..8f6eeb0a41 100644 --- a/common/test/common_tests/logic/variants_test.cljc +++ b/common/test/common_tests/logic/variants_test.cljc @@ -7,7 +7,7 @@ (ns common-tests.logic.variants-test (:require [app.common.files.changes-builder :as pcb] - [app.common.logic.variants :as clv] + [app.common.logic.variant-properties :as clvp] [app.common.test-helpers.components :as thc] [app.common.test-helpers.files :as thf] [app.common.test-helpers.ids-map :as thi] @@ -20,7 +20,7 @@ (t/deftest test-update-property-name (let [;; ==== Setup file (-> (thf/sample-file :file1) - (thv/add-variant :v01 :c01 :m01 :c02 :m02)) + (thv/add-variant-two-properties :v01 :c01 :m01 :c02 :m02)) v-id (-> (ths/get-shape file :v01) :id) page (thf/current-page file) @@ -29,8 +29,8 @@ (pcb/with-page-id (:id page)) (pcb/with-library-data (:data file)) (pcb/with-objects (:objects page)) - (clv/generate-update-property-name v-id 0 "NewName1") - (clv/generate-update-property-name v-id 1 "NewName2")) + (clvp/generate-update-property-name v-id 0 "NewName1") + (clvp/generate-update-property-name v-id 1 "NewName2")) file' (thf/apply-changes file changes) @@ -65,7 +65,7 @@ (pcb/with-page-id (:id page)) (pcb/with-library-data (:data file)) (pcb/with-objects (:objects page)) - (clv/generate-add-new-property v-id)) + (clvp/generate-add-new-property v-id)) file' (thf/apply-changes file changes) @@ -101,7 +101,7 @@ (pcb/with-page-id (:id page)) (pcb/with-library-data (:data file)) (pcb/with-objects (:objects page)) - (clv/generate-add-new-property v-id {:fill-values? true})) + (clvp/generate-add-new-property v-id {:fill-values? true})) file' (thf/apply-changes file changes) @@ -132,7 +132,7 @@ (pcb/with-page-id (:id page)) (pcb/with-library-data (:data file)) (pcb/with-objects (:objects page)) - (clv/generate-add-new-property v-id)) + (clvp/generate-add-new-property v-id)) file (thf/apply-changes file changes) @@ -147,7 +147,7 @@ (pcb/with-page-id (:id page)) (pcb/with-library-data (:data file)) (pcb/with-objects (:objects page)) - (clv/generate-remove-property v-id 0)) + (clvp/generate-remove-property v-id 0)) file' (thf/apply-changes file changes) @@ -180,8 +180,8 @@ (pcb/with-page-id (:id page)) (pcb/with-library-data (:data file)) (pcb/with-objects (:objects page)) - (clv/generate-update-property-value (:id comp01) 0 "NewValue1") - (clv/generate-update-property-value (:id comp02) 0 "NewValue2")) + (clvp/generate-update-property-value (:id comp01) 0 "NewValue1") + (clvp/generate-update-property-value (:id comp02) 0 "NewValue2")) file' (thf/apply-changes file changes) diff --git a/frontend/src/app/main/data/workspace/variants.cljs b/frontend/src/app/main/data/workspace/variants.cljs index 1c38fc82f0..b09ff188ba 100644 --- a/frontend/src/app/main/data/workspace/variants.cljs +++ b/frontend/src/app/main/data/workspace/variants.cljs @@ -8,10 +8,9 @@ (:require [app.common.colors :as clr] [app.common.data :as d] - [app.common.data.macros :as dm] [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] - [app.common.logic.libraries :as cll] + [app.common.logic.variant-properties :as clvp] [app.common.logic.variants :as clv] [app.common.types.component :as ctc] [app.common.types.components-list :as ctkl] @@ -29,18 +28,6 @@ [beicon.v2.core :as rx] [potok.v2.core :as ptk])) -(dm/export clv/find-related-components) - -(defn is-secondary-variant? - [component data] - (if-let [variant-id (:variant-id component)] - (let [page-id (:main-instance-page component) - objects (-> (dsh/get-page data page-id) - (get :objects)) - shapes (dm/get-in objects [variant-id :shapes])] - (not= (:main-instance-id component) (last shapes))) - false)) - (defn update-property-name "Update the variant property name on the position pos in all the components with this variant-id" @@ -56,7 +43,7 @@ changes (-> (pcb/empty-changes it page-id) (pcb/with-objects objects) (pcb/with-library-data data) - (clv/generate-update-property-name variant-id pos new-name)) + (clvp/generate-update-property-name variant-id pos new-name)) undo-id (js/Symbol)] (rx/of (dwu/start-undo-transaction undo-id) @@ -77,7 +64,7 @@ changes (-> (pcb/empty-changes it page-id) (pcb/with-library-data data) (pcb/with-objects objects) - (clv/generate-update-property-value component-id pos value)) + (clvp/generate-update-property-value component-id pos value)) undo-id (js/Symbol)] (rx/of (dwu/start-undo-transaction undo-id) @@ -100,7 +87,7 @@ changes (-> (pcb/empty-changes it page-id) (pcb/with-library-data data) (pcb/with-objects objects) - (clv/generate-remove-property variant-id pos)) + (clvp/generate-remove-property variant-id pos)) undo-id (js/Symbol)] (rx/of @@ -124,7 +111,7 @@ changes (-> (pcb/empty-changes it page-id) (pcb/with-library-data data) (pcb/with-objects objects) - (clv/generate-add-new-property variant-id options)) + (clvp/generate-add-new-property variant-id options)) undo-id (js/Symbol)] (rx/of @@ -132,7 +119,7 @@ (dch/commit-changes changes) (dwu/commit-undo-transaction undo-id)))))) -(defn set-variant-id +(defn- set-variant-id "Sets the variant-id on a component" [component-id variant-id] (ptk/reify ::set-variant-id @@ -149,7 +136,7 @@ (dch/commit-changes changes) (dwu/commit-undo-transaction undo-id)))))) -(defn focus-property +(defn- focus-property [shape-id prop-num] (ptk/reify ::focus-property ptk/EffectEvent @@ -177,30 +164,13 @@ new-component-id (uuid/next) new-shape-id (uuid/next) - value (str clv/value-prefix - (-> (clv/extract-properties-values data objects (:variant-id component)) - last - :value - count - inc)) - prop-num (dec (count (:variant-properties component))) - - [new-shape changes] (-> (pcb/empty-changes it page-id) + changes (-> (pcb/empty-changes it page-id) (pcb/with-library-data data) (pcb/with-objects objects) (pcb/with-page-id page-id) - (cll/generate-duplicate-component - {:data data} - component-id - new-component-id - true - {:new-shape-id new-shape-id :apply-changes-local-library? true})) - - changes (-> changes - (clv/generate-update-property-value new-component-id prop-num value) - (pcb/change-parent (:parent-id shape) [new-shape] 0)) + (clv/generate-add-new-variant shape (:variant-id component) new-component-id new-shape-id prop-num)) undo-id (js/Symbol)] (rx/concat @@ -253,6 +223,8 @@ undo-id (js/Symbol)] + ;;TODO Refactor all called methods in order to be able to + ;;generate changes instead of call the events (rx/concat (rx/of (dwu/start-undo-transaction undo-id) @@ -286,6 +258,7 @@ (dwu/commit-undo-transaction undo-id))))))) (defn add-component-or-variant + "Manage the shared shortcut, and do the pertinent action" [] (ptk/reify ::add-component-or-variant @@ -319,6 +292,7 @@ (rx/of (dwl/add-component))))))) (defn duplicate-or-add-variant + "Manage the shared shortcut, and do the pertinent action" [] (ptk/reify ::duplicate-or-add-variant ptk/WatchEvent diff --git a/frontend/src/app/main/ui/inspect/attributes/variant.cljs b/frontend/src/app/main/ui/inspect/attributes/variant.cljs index 25ccf8f651..13855a19a5 100644 --- a/frontend/src/app/main/ui/inspect/attributes/variant.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/variant.cljs @@ -8,7 +8,7 @@ (:require-macros [app.main.style :as stl]) (:require [app.common.data.macros :as dm] - [app.common.logic.variants :as clv] + [app.common.files.variant :as cfv] [app.common.types.component :as ctc] [app.common.types.components-list :as ctkl] [app.main.ui.components.copy-button :refer [copy-button*]] @@ -35,7 +35,7 @@ is-container? (ctc/is-variant-container? shape) component (when-not is-container? (ctkl/get-component data (:component-id shape)))] (if is-container? - (->> (clv/extract-properties-values data objects (:id shape)) + (->> (cfv/extract-properties-values data objects (:id shape)) (map #(update % :value (partial str/join ", ")))) (->> (:variant-properties component) (map #(update % :value (fn [v] (if (str/blank? v) "--" v))))))))] diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/file_library.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/file_library.cljs index e8f3bc58b8..a24bb61ca3 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/file_library.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/file_library.cljs @@ -9,12 +9,12 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.files.variant :as cfv] [app.common.types.components-list :as ctkl] [app.main.data.event :as ev] [app.main.data.workspace :as dw] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.undo :as dwu] - [app.main.data.workspace.variants :as dwv] [app.main.refs :as refs] [app.main.router :as rt] [app.main.store :as st] @@ -346,7 +346,7 @@ (mf/with-memo [filters library] (as-> (into [] (ctkl/components-seq library)) $ (cmm/apply-filters $ filters) - (remove #(dwv/is-secondary-variant? % library) $))) + (remove #(cfv/is-secondary-variant? % library) $))) filtered-media (mf/with-memo [filters media] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs index d9661d7d99..877b05b579 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs @@ -9,7 +9,7 @@ (:require [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] - [app.common.logic.variants :as clv] + [app.common.files.variant :as cfv] [app.common.types.component :as ctk] [app.common.types.file :as ctf] [app.main.data.helpers :as dsh] @@ -241,7 +241,7 @@ objects (-> (dsh/get-page data page-id) (get :objects)) - related-components (dwv/find-related-components data objects variant-id) + related-components (cfv/find-variant-components data objects variant-id) flat-comps ;; Get a list like [{:id 0 :prop1 "v1" :prop2 "v2"} {:id 1, :prop1 "v3" :prop2 "v4"}] (map (fn [{:keys [id variant-properties]}] @@ -760,7 +760,7 @@ variant-id (:variant-id first-variant) properties (mf/with-memo [data objects variant-id] - (clv/extract-properties-values data objects (:id shape))) + (cfv/extract-properties-values data objects (:id shape))) menu-open* (mf/use-state false) menu-open? (deref menu-open*)