mirror of
https://github.com/penpot/penpot.git
synced 2025-04-29 19:16:20 +02:00
439 lines
17 KiB
Clojure
439 lines
17 KiB
Clojure
;; 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.main.data.workspace.variants
|
|
(:require
|
|
[app.common.colors :as clr]
|
|
[app.common.data :as d]
|
|
[app.common.files.changes-builder :as pcb]
|
|
[app.common.files.helpers :as cfh]
|
|
[app.common.files.variant :as cfv]
|
|
[app.common.geom.point :as gpt]
|
|
[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]
|
|
[app.common.types.shape.layout :as ctsl]
|
|
[app.common.types.variant :as ctv]
|
|
[app.common.uuid :as uuid]
|
|
[app.main.data.changes :as dch]
|
|
[app.main.data.helpers :as dsh]
|
|
[app.main.data.workspace.colors :as cl]
|
|
[app.main.data.workspace.libraries :as dwl]
|
|
[app.main.data.workspace.selection :as dws]
|
|
[app.main.data.workspace.shape-layout :as dwsl]
|
|
[app.main.data.workspace.shapes :as dwsh]
|
|
[app.main.data.workspace.transforms :as dwt]
|
|
[app.main.data.workspace.undo :as dwu]
|
|
[app.main.features :as features]
|
|
[app.util.dom :as dom]
|
|
[beicon.v2.core :as rx]
|
|
[potok.v2.core :as ptk]))
|
|
|
|
(defn update-properties-names-and-values
|
|
"Compares the previous properties with the updated ones and executes the correspondent action
|
|
for each one depending on if it needs to be removed, updated or added"
|
|
[component-id variant-id previous-properties updated-properties]
|
|
(ptk/reify ::update-properties-names-and-values
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(update state :workspace-local dissoc :shape-for-rename))
|
|
|
|
ptk/WatchEvent
|
|
(watch [it state _]
|
|
(let [page-id (:current-page-id state)
|
|
data (dsh/lookup-file-data state)
|
|
objects (-> (dsh/get-page data page-id)
|
|
(get :objects))
|
|
|
|
properties-to-remove (ctv/find-properties-to-remove previous-properties updated-properties)
|
|
properties-to-add (ctv/find-properties-to-add previous-properties updated-properties)
|
|
properties-to-update (ctv/find-properties-to-update previous-properties updated-properties)
|
|
|
|
changes (-> (pcb/empty-changes it page-id)
|
|
(pcb/with-objects objects)
|
|
(pcb/with-library-data data))
|
|
|
|
changes (reduce
|
|
(fn [changes {:keys [name]}]
|
|
(-> changes
|
|
(clvp/generate-update-property-value component-id (ctv/find-index-for-property-name previous-properties name) "")))
|
|
changes
|
|
properties-to-remove)
|
|
|
|
changes (reduce
|
|
(fn [changes {:keys [name value]}]
|
|
(-> changes
|
|
(clvp/generate-update-property-value component-id (ctv/find-index-for-property-name previous-properties name) value)))
|
|
changes
|
|
properties-to-update)
|
|
|
|
changes (reduce
|
|
(fn [changes [idx {:keys [name value]}]]
|
|
(-> changes
|
|
(clvp/generate-add-new-property variant-id {:property-name name})
|
|
(clvp/generate-update-property-value component-id (+ idx (count previous-properties)) value)))
|
|
changes
|
|
(map-indexed vector properties-to-add))
|
|
|
|
undo-id (js/Symbol)]
|
|
|
|
(rx/of
|
|
(dwu/start-undo-transaction undo-id)
|
|
(dch/commit-changes changes)
|
|
(dwu/commit-undo-transaction undo-id))))))
|
|
|
|
(defn update-property-name
|
|
"Update the variant property name on the position pos
|
|
in all the components with this variant-id"
|
|
[variant-id pos new-name]
|
|
(ptk/reify ::update-property-name
|
|
ptk/WatchEvent
|
|
(watch [it state _]
|
|
(let [page-id (:current-page-id state)
|
|
data (dsh/lookup-file-data state)
|
|
objects (-> (dsh/get-page data page-id)
|
|
(get :objects))
|
|
|
|
changes (-> (pcb/empty-changes it page-id)
|
|
(pcb/with-objects objects)
|
|
(pcb/with-library-data data)
|
|
(clvp/generate-update-property-name variant-id pos new-name))
|
|
undo-id (js/Symbol)]
|
|
(rx/of
|
|
(dwu/start-undo-transaction undo-id)
|
|
(dch/commit-changes changes)
|
|
(dwu/commit-undo-transaction undo-id))))))
|
|
|
|
(defn update-property-value
|
|
"Updates the variant property value on the position pos in a component"
|
|
[component-id pos value]
|
|
(ptk/reify ::update-property-value
|
|
ptk/WatchEvent
|
|
(watch [it state _]
|
|
(let [page-id (:current-page-id state)
|
|
data (dsh/lookup-file-data state)
|
|
objects (-> (dsh/get-page data page-id)
|
|
(get :objects))
|
|
|
|
changes (-> (pcb/empty-changes it page-id)
|
|
(pcb/with-library-data data)
|
|
(pcb/with-objects objects)
|
|
(clvp/generate-update-property-value component-id pos value))
|
|
undo-id (js/Symbol)]
|
|
(rx/of
|
|
(dwu/start-undo-transaction undo-id)
|
|
(dch/commit-changes changes)
|
|
(dwu/commit-undo-transaction undo-id))))))
|
|
|
|
|
|
(defn remove-property
|
|
"Remove the variant property on the position pos
|
|
in all the components with this variant-id"
|
|
[variant-id pos]
|
|
(ptk/reify ::remove-property
|
|
ptk/WatchEvent
|
|
(watch [it state _]
|
|
(let [page-id (:current-page-id state)
|
|
data (dsh/lookup-file-data state)
|
|
objects (-> (dsh/get-page data page-id)
|
|
(get :objects))
|
|
|
|
changes (-> (pcb/empty-changes it page-id)
|
|
(pcb/with-library-data data)
|
|
(pcb/with-objects objects)
|
|
(clvp/generate-remove-property variant-id pos))
|
|
|
|
undo-id (js/Symbol)]
|
|
(rx/of
|
|
(dwu/start-undo-transaction undo-id)
|
|
(dch/commit-changes changes)
|
|
(dwu/commit-undo-transaction undo-id))))))
|
|
|
|
|
|
|
|
(defn add-new-property
|
|
"Add a new variant property to all the components with this variant-id"
|
|
[variant-id & [options]]
|
|
(ptk/reify ::add-new-property
|
|
ptk/WatchEvent
|
|
(watch [it state _]
|
|
(let [page-id (:current-page-id state)
|
|
data (dsh/lookup-file-data state)
|
|
objects (-> (dsh/get-page data page-id)
|
|
(get :objects))
|
|
|
|
changes (-> (pcb/empty-changes it page-id)
|
|
(pcb/with-library-data data)
|
|
(pcb/with-objects objects)
|
|
(clvp/generate-add-new-property variant-id options))
|
|
|
|
undo-id (js/Symbol)]
|
|
(rx/of
|
|
(dwu/start-undo-transaction undo-id)
|
|
(dch/commit-changes changes)
|
|
(dwu/commit-undo-transaction undo-id))))))
|
|
|
|
(defn- set-variant-id
|
|
"Sets the variant-id on a component"
|
|
[component-id variant-id]
|
|
(ptk/reify ::set-variant-id
|
|
ptk/WatchEvent
|
|
(watch [it state _]
|
|
(let [page-id (:current-page-id state)
|
|
data (dsh/lookup-file-data state)
|
|
changes (-> (pcb/empty-changes it page-id)
|
|
(pcb/with-library-data data)
|
|
(pcb/update-component component-id #(assoc % :variant-id variant-id)))
|
|
undo-id (js/Symbol)]
|
|
(rx/of
|
|
(dwu/start-undo-transaction undo-id)
|
|
(dch/commit-changes changes)
|
|
(dwu/commit-undo-transaction undo-id))))))
|
|
|
|
(defn- focus-property
|
|
[shape-id prop-num]
|
|
(ptk/reify ::focus-property
|
|
ptk/EffectEvent
|
|
(effect [_ _ _]
|
|
(dom/focus! (dom/get-element (str "variant-prop-" shape-id prop-num))))))
|
|
|
|
|
|
(defn- resposition-and-resize-variant
|
|
"Resize the variant container, and move the shape (that is a variant) to the right"
|
|
[shape-id]
|
|
(ptk/reify ::resposition-and-resize-variant
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(let [page-id (:current-page-id state)
|
|
objects (dsh/lookup-page-objects state page-id)
|
|
shape (get objects shape-id)
|
|
container (get objects (:parent-id shape))
|
|
width (+ (:width container) (:width shape) 20) ;; 20 is the default gap for variants
|
|
x (- width (+ (:width shape) 30))] ;; 30 is the default margin for variants
|
|
(rx/of
|
|
(dwt/update-dimensions [(:parent-id shape)] :width width)
|
|
(dwt/update-position shape-id
|
|
{:x x}
|
|
{:absolute? false}))))))
|
|
|
|
|
|
(defn add-new-variant
|
|
"Create a new variant and add it to the variant-container"
|
|
[shape-id]
|
|
(ptk/reify ::add-new-variant
|
|
ptk/WatchEvent
|
|
(watch [it state _]
|
|
(let [page-id (:current-page-id state)
|
|
data (dsh/lookup-file-data state)
|
|
objects (-> (dsh/get-page data page-id)
|
|
(get :objects))
|
|
shape (get objects shape-id)
|
|
shape (if (ctc/is-variant-container? shape)
|
|
(get objects (last (:shapes shape)))
|
|
shape)
|
|
component-id (:component-id shape)
|
|
component (ctkl/get-component data component-id)
|
|
|
|
container-id (:parent-id shape)
|
|
variant-container (get objects container-id)
|
|
has-layout? (ctsl/any-layout? variant-container)
|
|
|
|
new-component-id (uuid/next)
|
|
new-shape-id (uuid/next)
|
|
|
|
prop-num (dec (count (:variant-properties component)))
|
|
|
|
changes (-> (pcb/empty-changes it page-id)
|
|
(pcb/with-library-data data)
|
|
(pcb/with-objects objects)
|
|
(pcb/with-page-id page-id)
|
|
(clv/generate-add-new-variant shape (:variant-id component) new-component-id new-shape-id prop-num))
|
|
|
|
undo-id (js/Symbol)]
|
|
(rx/concat
|
|
(rx/of
|
|
(dwu/start-undo-transaction undo-id)
|
|
(dch/commit-changes changes)
|
|
(when-not has-layout?
|
|
(resposition-and-resize-variant new-shape-id))
|
|
(dwu/commit-undo-transaction undo-id)
|
|
(ptk/data-event :layout/update {:ids [(:parent-id shape)]})
|
|
(dws/select-shape new-shape-id))
|
|
(->> (rx/of (focus-property new-shape-id prop-num))
|
|
(rx/delay 250)))))))
|
|
|
|
(defn transform-in-variant
|
|
"Given the id of a main shape of a component, creates a variant structure for
|
|
that component"
|
|
[main-instance-id]
|
|
(ptk/reify ::transform-in-variant
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(let [variant-id (uuid/next)
|
|
variant-vec [variant-id]
|
|
file-id (:current-file-id state)
|
|
page-id (:current-page-id state)
|
|
objects (dsh/lookup-page-objects state file-id page-id)
|
|
main (get objects main-instance-id)
|
|
parent (get objects (:parent-id main))
|
|
component-id (:component-id main)
|
|
cpath (cfh/split-path (:name main))
|
|
name (first cpath)
|
|
num-props (max 1 (dec (count cpath)))
|
|
cont-props {:layout-item-h-sizing :auto
|
|
:layout-item-v-sizing :auto
|
|
:layout-padding {:p1 30 :p2 30 :p3 30 :p4 30}
|
|
:layout-gap {:row-gap 0 :column-gap 20}
|
|
:name name
|
|
:r1 20
|
|
:r2 20
|
|
:r3 20
|
|
:r4 20
|
|
:is-variant-container true}
|
|
main-props {:layout-item-h-sizing :fix
|
|
:layout-item-v-sizing :fix
|
|
:variant-id variant-id
|
|
:name name}
|
|
stroke-props {:stroke-alignment :inner
|
|
:stroke-style :solid
|
|
:stroke-color "#bb97d8" ;; todo use color var?
|
|
:stroke-opacity 1
|
|
:stroke-width 2}
|
|
|
|
;; Move the position of the variant container so the main shape doesn't
|
|
;; change its position
|
|
delta (if (ctsl/any-layout? parent)
|
|
(gpt/point 0 0)
|
|
(gpt/point -30 -30))
|
|
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)
|
|
|
|
(when (not= name (:name main))
|
|
(dwl/rename-component component-id name))
|
|
|
|
;; Create variant container
|
|
(dwsh/create-artboard-from-selection variant-id nil nil nil delta)
|
|
(cl/remove-all-fills variant-vec {:color clr/black :opacity 1})
|
|
(dwsl/create-layout-from-id variant-id :flex)
|
|
(dwsh/update-shapes variant-vec #(merge % cont-props))
|
|
(dwsh/update-shapes [main-instance-id] #(merge % main-props))
|
|
(cl/add-stroke variant-vec stroke-props)
|
|
(set-variant-id component-id variant-id))
|
|
|
|
;; Add the necessary number of new properties, with default values
|
|
(rx/from
|
|
(repeatedly num-props
|
|
#(add-new-property variant-id {:fill-values? true})))
|
|
|
|
;; When the component has path, set the path items as properties values
|
|
(when (> (count cpath) 1)
|
|
(rx/from
|
|
(map
|
|
#(update-property-value component-id % (nth cpath (inc %)))
|
|
(range num-props))))
|
|
|
|
(rx/of
|
|
(add-new-variant main-instance-id)
|
|
(dwu/commit-undo-transaction undo-id)
|
|
(ptk/data-event :layout/update {:ids [variant-id]})))))))
|
|
|
|
(defn add-component-or-variant
|
|
"Manage the shared shortcut, and do the pertinent action"
|
|
[]
|
|
(ptk/reify ::add-component-or-variant
|
|
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(let [variants? (features/active-feature? state "variants/v1")
|
|
objects (dsh/lookup-page-objects state)
|
|
selected-ids (dsh/lookup-selected state)
|
|
selected-shapes (map (d/getf objects) selected-ids)
|
|
single? (= 1 (count selected-ids))
|
|
first-shape (first selected-shapes)
|
|
|
|
transform-in-variant? (and variants?
|
|
single?
|
|
(not (ctc/is-variant? first-shape))
|
|
(ctc/main-instance? first-shape))
|
|
add-new-variant? (and variants?
|
|
(every? ctc/is-variant? selected-shapes))
|
|
undo-id (js/Symbol)]
|
|
(cond
|
|
transform-in-variant?
|
|
(rx/of (transform-in-variant (:id first-shape)))
|
|
|
|
add-new-variant?
|
|
(rx/concat
|
|
(rx/of (dwu/start-undo-transaction undo-id))
|
|
(rx/from (map add-new-variant selected-ids))
|
|
(rx/of (dwu/commit-undo-transaction undo-id)))
|
|
|
|
:else
|
|
(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
|
|
(watch [_ state _]
|
|
(let [variants? (features/active-feature? state "variants/v1")
|
|
objects (dsh/lookup-page-objects state)
|
|
selected-ids (dsh/lookup-selected state)
|
|
selected-shapes (map (d/getf objects) selected-ids)
|
|
add-new-variant? (and variants?
|
|
(every? ctc/is-variant? selected-shapes))
|
|
undo-id (js/Symbol)]
|
|
(if add-new-variant?
|
|
(rx/concat
|
|
(rx/of (dwu/start-undo-transaction undo-id))
|
|
(rx/from (map add-new-variant selected-ids))
|
|
(rx/of (dwu/commit-undo-transaction undo-id)))
|
|
(rx/of (dws/duplicate-selected true)))))))
|
|
|
|
|
|
(defn rename-variant
|
|
[variant-id name]
|
|
(ptk/reify ::rename-variant
|
|
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(let [page-id (:current-page-id state)
|
|
data (dsh/lookup-file-data state)
|
|
objects (-> (dsh/get-page data page-id)
|
|
(get :objects))
|
|
variant-components (cfv/find-variant-components data objects variant-id)
|
|
clean-name (cfh/clean-path name)
|
|
undo-id (js/Symbol)]
|
|
|
|
(rx/concat
|
|
(rx/of (dwu/start-undo-transaction undo-id)
|
|
(dwsh/update-shapes [variant-id] #(assoc % :name clean-name)))
|
|
(rx/from (map
|
|
#(dwl/rename-component-and-main-instance (:id %) clean-name)
|
|
variant-components))
|
|
(rx/of (dwu/commit-undo-transaction undo-id)))))))
|
|
|
|
|
|
(defn rename-comp-or-variant-and-main
|
|
[component-id name]
|
|
(ptk/reify ::rename-comp-or-variant-and-main
|
|
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(let [data (dsh/lookup-file-data state)
|
|
component (ctkl/get-component data component-id)]
|
|
(if (ctc/is-variant? component)
|
|
(rx/of (rename-variant (:variant-id component) name))
|
|
(rx/of (dwl/rename-component-and-main-instance component-id name)))))))
|
|
|