Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Andrey Antukh 2025-03-20 14:27:37 +01:00
commit 22cd43b8a2
33 changed files with 634 additions and 655 deletions

View file

@ -12,6 +12,7 @@
### :bug: Bugs fixed
## 2.6.0 (Unreleased)
### :rocket: Epics and highlights
@ -35,6 +36,9 @@
- Fix duplicate page with component over frame [Taiga #8151](https://tree.taiga.io/project/penpot/issue/8151) and [Taiga #9698](https://tree.taiga.io/project/penpot/issue/9698)
- The plugin list in the navigation menu lacks scrolling, some plugins are not visible when a large number are installed [Taiga #9360](https://tree.taiga.io/project/penpot/us/9360)
- Fix hidden toolbar click event still available [Taiga #10437](https://tree.taiga.io/project/penpot/us/10437)
- Fix hovering over templates [Taiga #10545](https://tree.taiga.io/project/penpot/issue/10545)
- Fix problem with default shadows value in plugins [Plugins #191](https://github.com/penpot/penpot-plugins/issues/191)
- Fix problem with constraints when creating group [Taiga #10455](https://tree.taiga.io/project/penpot/issue/10455)
## 2.5.4

View file

@ -10,6 +10,7 @@
(:require
[app.binfile.common :as bfc]
[app.common.data :as d]
[app.common.files.migrations :as fmg]
[app.common.files.validate :as cfv]
[app.db :as db]
[app.features.components-v2 :as feat.comp-v2]
@ -142,7 +143,9 @@
(update-fn file opts)))]
(when (and (some? file')
(not (identical? file file')))
(or (fmg/migrated? file)
(not (identical? file file'))))
(when validate?
(cfv/validate-file-schema! file'))

View file

@ -61,7 +61,8 @@
"styles/v2"
"layout/grid"
"components/v2"
"plugins/runtime"})
"plugins/runtime"
"design-tokens/v1"})
;; A set of features which only affects on frontend and can be enabled
;; and disabled freely by the user any time. This features does not

View file

@ -382,13 +382,13 @@
[:set-group-path [:vector :string]]
[:set-group-fname :string]]]
[:move-token-set-before
[:map {:title "MoveTokenSetBefore"}
[:type [:= :move-token-set-before]]
[:move-token-set
[:map {:title "MoveTokenSet"}
[:type [:= :move-token-set]]
[:from-path [:vector :string]]
[:to-path [:vector :string]]
[:before-path [:maybe [:vector :string]]]
[:before-group? [:maybe :boolean]]]]
[:before-group [:maybe :boolean]]]]
[:move-token-set-group-before
[:map {:title "MoveTokenSetGroupBefore"}
@ -1051,11 +1051,11 @@
(ctob/ensure-tokens-lib)
(ctob/rename-set-group set-group-path set-group-fname)))))
(defmethod process-change :move-token-set-before
[data {:keys [from-path to-path before-path before-group?] :as changes}]
(defmethod process-change :move-token-set
[data {:keys [from-path to-path before-path before-group] :as changes}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/move-set from-path to-path before-path before-group?))))
(ctob/move-set from-path to-path before-path before-group))))
(defmethod process-change :move-token-set-group-before
[data {:keys [from-path to-path before-path before-group?]}]

View file

@ -809,19 +809,19 @@
(update :undo-changes conj {:type :rename-token-set-group :set-group-path undo-path :set-group-fname undo-fname})
(apply-changes-local))))
(defn move-token-set-before
(defn move-token-set
[changes {:keys [from-path to-path before-path before-group? prev-before-path prev-before-group?] :as opts}]
(-> changes
(update :redo-changes conj {:type :move-token-set-before
(update :redo-changes conj {:type :move-token-set
:from-path from-path
:to-path to-path
:before-path before-path
:before-group? before-group?})
(update :undo-changes conj {:type :move-token-set-before
:before-group before-group?})
(update :undo-changes conj {:type :move-token-set
:from-path to-path
:to-path from-path
:before-path prev-before-path
:before-group? prev-before-group?})
:before-group prev-before-group?})
(apply-changes-local)))
(defn move-token-set-group-before

View file

@ -283,6 +283,22 @@
:else
(get-root-frame objects (:frame-id frame)))))
(defn get-parent-frame
"Similar to `get-frame, but always return the parent frame. When root
frame is provided, then itself is returned."
[objects shape-or-id]
(cond
(map? shape-or-id)
(get objects (dm/get-prop shape-or-id :frame-id))
(= uuid/zero shape-or-id)
(get objects uuid/zero)
:else
(some->> shape-or-id
(get objects)
(get-frame objects))))
(defn valid-frame-target?
[objects parent-id shape-id]
(let [shape (get objects shape-id)]

View file

@ -29,7 +29,6 @@
[app.common.types.file :as ctf]
[app.common.types.shape :as cts]
[app.common.types.shape.shadow :as ctss]
[app.common.types.tokens-lib :as ctob]
[app.common.uuid :as uuid]
[clojure.set :as set]
[cuerdas.core :as str]))
@ -97,13 +96,13 @@
(if (nil? migrations)
(generate-migrations-from-version version)
migrations)))
(migrate)
(update :features (fnil into #{}) (deref cfeat/*new*))
;; NOTE: in some future we can consider to apply
;; a migration to the whole database and remove
;; this code from this function that executes on
;; each file migration operation
(update :features cfeat/migrate-legacy-features)))))
(update :features cfeat/migrate-legacy-features)
(migrate)))))
(defn migrated?
[file]
@ -1226,32 +1225,7 @@
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(defmethod migrate-data "Ensure hidden theme"
[data _]
(letfn [(update-tokens-lib [tokens-lib]
(let [hidden-theme (ctob/get-hidden-theme tokens-lib)]
(if (nil? hidden-theme)
(ctob/add-theme tokens-lib (ctob/make-hidden-token-theme))
tokens-lib)))]
(if (contains? data :tokens-lib)
(update data :tokens-lib update-tokens-lib)
data)))
(defmethod migrate-data "Add token theme id"
[data _]
(letfn [(update-tokens-lib [tokens-lib]
(let [themes (ctob/get-themes tokens-lib)]
(reduce (fn [lib theme]
(if (:id theme)
lib
(ctob/update-theme lib (:group theme) (:name theme) #(assoc % :id (str (uuid/next))))))
tokens-lib
themes)))]
(if (contains? data :tokens-lib)
(update data :tokens-lib update-tokens-lib)
data)))
(defmethod migrate-data "Remove tokens from groups"
(defmethod migrate-data "0001-remove-tokens-from-groups"
[data _]
(letfn [(update-object [object]
(cond-> object
@ -1320,6 +1294,4 @@
"legacy-65"
"legacy-66"
"legacy-67"
"Ensure hidden theme"
"Add token theme id"
"Remove tokens from groups"]))
"0001-remove-tokens-from-groups"]))

View file

@ -1,11 +1,21 @@
;; 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.tokens
(:require
[app.common.files.changes-builder :as pcb]
[app.common.types.tokens-lib :as ctob]))
(defn generate-update-active-sets
"Copy the active sets from the currently active themes and move them to the hidden token theme and update the theme with `update-theme-fn`.
Use this for managing sets active state without having to modify a user created theme (\"no themes selected\" state in the ui)."
"Copy the active sets from the currently active themes and move them
to the hidden token theme and update the theme with
`update-theme-fn`.
Use this for managing sets active state without having to modify a
user created theme (\"no themes selected\" state in the ui)."
[changes tokens-lib update-theme-fn]
(let [prev-active-token-themes (ctob/get-active-theme-paths tokens-lib)
active-token-set-names (ctob/get-active-themes-set-names tokens-lib)
@ -21,7 +31,8 @@
hidden-token-theme))))
(defn generate-toggle-token-set
"Toggle a token set at `set-name` in `tokens-lib` without modifying a user theme."
"Toggle a token set at `set-name` in `tokens-lib` without modifying a
user theme."
[changes tokens-lib set-name]
(generate-update-active-sets changes tokens-lib #(ctob/toggle-set % set-name)))
@ -49,12 +60,20 @@
:or {collapsed-paths #{}}}]
(let [tree (-> (ctob/get-set-tree tokens-lib)
(ctob/walk-sets-tree-seq :skip-children-pred #(contains? collapsed-paths %)))
from (nth tree from-index)
to (nth tree to-index)
before (case position
:top to
:bot (nth tree (inc to-index) nil)
:bot (let [v (nth tree (inc to-index) nil)]
;; if the next index is a group, we need to set it as
;; nil because if we set a path on different subpath,
;; the move algorightm will simply remove the set
(if (:group? v)
nil
v))
:center nil)
prev-before (if (:group? from)
(->> (drop (inc from-index) tree)
(filter (fn [element]
@ -113,9 +132,9 @@
(defn generate-move-token-set
"Create changes for dropping a token set or token set.
Throws for impossible moves."
[changes tokens-lib drop-opts]
(if-let [drop-opts' (calculate-move-token-set-or-set-group tokens-lib drop-opts)]
(pcb/move-token-set-before changes drop-opts')
[changes tokens-lib params]
(if-let [params (calculate-move-token-set-or-set-group tokens-lib params)]
(pcb/move-token-set changes params)
changes))
(defn generate-move-token-set-group

View file

@ -1019,26 +1019,26 @@
(def valid-text?
(validator ::text))
(def check-safe-int!
(def check-safe-int
(check-fn ::safe-int))
(def check-set-of-strings!
(def check-set-of-strings
(check-fn ::set-of-strings))
(def check-email!
(def check-email
(check-fn ::email))
(def check-uuid!
(def check-uuid
(check-fn ::uuid :hint "expected valid uuid instance"))
(def check-string!
(def check-string
(check-fn :string :hint "expected string"))
(def check-coll-of-uuid!
(def check-coll-of-uuid
(check-fn ::coll-of-uuid))
(def check-set-of-uuid!
(def check-set-of-uuid
(check-fn ::set-of-uuid))
(def check-set-of-emails!
(def check-set-of-emails
(check-fn [::set ::email]))

View file

@ -7,7 +7,6 @@
(ns app.common.types.shape.interactions
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.geom.point :as gpt]
[app.common.geom.shapes.bounds :as gsb]
@ -180,7 +179,7 @@
(sm/register! ::interaction schema:interaction)
(def check-interaction!
(def check-interaction
(sm/check-fn schema:interaction))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -203,18 +202,13 @@
(defn set-event-type
[interaction event-type shape]
(dm/assert!
"Should be an interraction map"
(check-interaction! interaction))
(assert (check-interaction interaction))
(assert (contains? event-types event-type)
"should be a valid event type")
(dm/assert!
"Should be a valid event type"
(contains? event-types event-type))
(dm/assert!
"The `:after-delay` event type incompatible with not frame shapes"
(or (not= event-type :after-delay)
(cfh/frame-shape? shape)))
(assert (or (not= event-type :after-delay)
(cfh/frame-shape? shape))
"the `:after-delay` event type incompatible with not frame shapes")
(if (= (:event-type interaction) event-type)
interaction
@ -230,14 +224,9 @@
(defn set-action-type
[interaction action-type]
(dm/assert!
"Should be an interraction map"
(check-interaction! interaction))
(dm/assert!
"Should be a valid event type"
(contains? action-types action-type))
(assert (check-interaction interaction))
(assert (contains? action-types action-type)
"Should be a valid event type")
(let [new-interaction
(if (= (:action-type interaction) action-type)
@ -284,18 +273,10 @@
(defn set-delay
[interaction delay]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected valid delay"
(sm/check-safe-int! delay))
(dm/assert!
"expected compatible interaction event type"
(has-delay interaction))
(assert (check-interaction interaction))
(assert (sm/check-safe-int delay))
(assert (has-delay interaction)
"expected compatible interaction event type")
(assoc interaction :delay delay))
@ -315,14 +296,9 @@
(defn set-destination
[interaction destination]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected compatible interaction event type"
(has-destination interaction))
(assert (check-interaction interaction))
(assert (has-destination interaction)
"expected compatible interaction event type")
(cond-> interaction
:always
@ -340,17 +316,11 @@
(defn set-preserve-scroll
[interaction preserve-scroll]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected boolean for `preserve-scroll`"
(boolean? preserve-scroll))
(dm/assert!
"expected compatible interaction map with preserve-scroll"
(has-preserve-scroll interaction))
(assert (check-interaction interaction))
(assert (boolean? preserve-scroll)
"expected boolean for `preserve-scroll`")
(assert (has-preserve-scroll interaction)
"expected compatible interaction map with preserve-scroll")
(assoc interaction :preserve-scroll preserve-scroll))
@ -361,17 +331,11 @@
(defn set-url
[interaction url]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected a string for `url`"
(string? url))
(dm/assert!
"expected compatible interaction map with url param"
(has-url interaction))
(assert (check-interaction interaction))
(assert (string? url)
"expected a string for `url`")
(assert (has-url interaction)
"expected compatible interaction map with url param")
(assoc interaction :url url))
@ -382,17 +346,12 @@
(defn set-overlay-pos-type
[interaction overlay-pos-type shape objects]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(assert (check-interaction interaction))
(dm/assert!
"expected valid overlay positioning type"
(contains? overlay-positioning-types overlay-pos-type))
(dm/assert!
"expected compatible interaction map"
(has-overlay-opts interaction))
(assert (contains? overlay-positioning-types overlay-pos-type)
"expected valid overlay positioning type")
(assert (has-overlay-opts interaction)
"expected compatible interaction map")
(assoc interaction
:overlay-pos-type overlay-pos-type
@ -403,17 +362,11 @@
(defn toggle-overlay-pos-type
[interaction overlay-pos-type shape objects]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected valid overlay positioning type"
(contains? overlay-positioning-types overlay-pos-type))
(dm/assert!
"expected compatible interaction map"
(has-overlay-opts interaction))
(assert (check-interaction interaction))
(assert (contains? overlay-positioning-types overlay-pos-type)
"expected valid overlay positioning type")
(assert (has-overlay-opts interaction)
"expected compatible interaction map")
(let [new-pos-type (if (= (:overlay-pos-type interaction) overlay-pos-type)
:manual
@ -427,17 +380,12 @@
(defn set-overlay-position
[interaction overlay-position]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(assert (check-interaction interaction))
(assert (gpt/point? overlay-position)
"expected valid overlay position")
(assert (has-overlay-opts interaction)
"expected compatible interaction map")
(dm/assert!
"expected valid overlay position"
(gpt/point? overlay-position))
(dm/assert!
"expected compatible interaction map"
(has-overlay-opts interaction))
(assoc interaction
:overlay-pos-type :manual
@ -446,52 +394,34 @@
(defn set-close-click-outside
[interaction close-click-outside]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected boolean value for `close-click-outside`"
(boolean? close-click-outside))
(dm/assert!
"expected compatible interaction map"
(has-overlay-opts interaction))
(assert (check-interaction interaction))
(assert (boolean? close-click-outside)
"expected boolean value for `close-click-outside`")
(assert (has-overlay-opts interaction)
"expected compatible interaction map")
(assoc interaction :close-click-outside close-click-outside))
(defn set-background-overlay
[interaction background-overlay]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected boolean value for `background-overlay`"
(boolean? background-overlay))
(dm/assert!
"expected compatible interaction map"
(has-overlay-opts interaction))
(assert (check-interaction interaction))
(assert (boolean? background-overlay)
"expected boolean value for `background-overlay`")
(assert (has-overlay-opts interaction)
"expected compatible interaction map")
(assoc interaction :background-overlay background-overlay))
(defn set-position-relative-to
[interaction position-relative-to]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected valid uuid for `position-relative-to`"
(or (nil? position-relative-to)
(uuid? position-relative-to)))
(dm/assert!
"expected compatible interaction map"
(has-overlay-opts interaction))
(assert (check-interaction interaction))
(assert (or (nil? position-relative-to)
(uuid? position-relative-to))
"expected valid uuid for `position-relative-to`")
(assert (has-overlay-opts interaction)
"expected compatible interaction map")
(assoc interaction :position-relative-to position-relative-to))
@ -519,13 +449,9 @@
frame-offset] ;; if this interaction starts in a frame opened
;; on another interaction, this is the position
;; of that frame
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected compatible interaction map"
(has-overlay-opts interaction))
(assert (check-interaction interaction))
(assert (has-overlay-opts interaction)
"expected compatible interaction map")
(let [;; When the interactive item is inside a nested frame we need to add to the offset the position
;; of the parent-frame otherwise the position won't match
@ -617,22 +543,15 @@
(defn set-animation-type
[interaction animation-type]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected valid value for `animation-type`"
(or (nil? animation-type)
(contains? animation-types animation-type)))
(dm/assert!
"expected interaction map compatible with animation"
(has-animation? interaction))
(dm/assert!
"expected allowed animation type"
(allowed-animation? (:action-type interaction) animation-type))
(assert (check-interaction interaction))
(assert (or (nil? animation-type)
(contains? animation-types animation-type))
"expected valid value for `animation-type`")
(assert (has-animation? interaction)
"expected interaction map compatible with animation")
(assert (allowed-animation? (:action-type interaction) animation-type)
"expected allowed animation type")
(if (= (-> interaction :animation :animation-type) animation-type)
interaction
@ -668,17 +587,10 @@
(defn set-duration
[interaction duration]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected valid duration"
(sm/check-safe-int! duration))
(dm/assert!
"expected compatible interaction map"
(has-duration? interaction))
(assert (check-interaction interaction))
(assert (sm/check-safe-int duration))
(assert (has-duration? interaction)
"expected compatible interaction map")
(update interaction :animation assoc :duration duration))
@ -689,17 +601,11 @@
(defn set-easing
[interaction easing]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected valid easing"
(contains? easing-types easing))
(dm/assert!
"expected compatible interaction map"
(has-easing? interaction))
(assert (check-interaction interaction))
(assert (contains? easing-types easing)
"expected valid easing")
(assert (has-easing? interaction)
"expected compatible interaction map")
(update interaction :animation assoc :easing easing))
@ -712,17 +618,11 @@
(defn set-way
[interaction way]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected valid way"
(contains? way-types way))
(dm/assert!
"expected compatible interaction map"
(has-way? interaction))
(assert (check-interaction interaction))
(assert (contains? way-types way)
"expected valid way")
(assert (has-way? interaction)
"expected compatible interaction map")
(update interaction :animation assoc :way way))
@ -733,26 +633,20 @@
(defn set-direction
[interaction direction]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(assert (check-interaction interaction))
(assert (contains? direction-types direction)
"expected valid direction")
(dm/assert!
"expected valid direction"
(contains? direction-types direction))
(dm/assert!
"expected compatible interaction map"
(has-direction? interaction))
(assert (has-direction? interaction)
"expected compatible interaction map")
(update interaction :animation assoc :direction direction))
(defn invert-direction
[animation]
(dm/assert!
"expected valid animation map"
(or (nil? animation)
(check-animation! animation)))
(assert (or (nil? animation)
(check-animation! animation))
"expected valid animation map")
(case (:direction animation)
:right
@ -768,24 +662,18 @@
(defn has-offset-effect?
[interaction]
; Offset-effect is ignored in slide animations of overlay actions
;; Offset-effect is ignored in slide animations of overlay actions
(and (= (:action-type interaction) :navigate)
(= (-> interaction :animation :animation-type) :slide)))
(defn set-offset-effect
[interaction offset-effect]
(dm/assert!
"expected valid interaction map"
(check-interaction! interaction))
(dm/assert!
"expected valid boolean for `offset-effect`"
(boolean? offset-effect))
(dm/assert!
"expected compatible interaction map"
(has-offset-effect? interaction))
(assert (check-interaction interaction))
(assert (boolean? offset-effect)
"expected valid boolean for `offset-effect`")
(assert (has-offset-effect? interaction)
"expected compatible interaction map")
(update interaction :animation assoc :offset-effect offset-effect))

View file

@ -588,7 +588,7 @@
(update :group d/nilv top-level-theme-group-name)
(update :description d/nilv "")
(update :is-source d/nilv false)
(update :id d/nilv (str (uuid/next)))
(update :id #(or % (str (uuid/next))))
(update :modified-at #(or % (dt/now)))
(update :sets set)
(check-token-theme-attrs)
@ -612,7 +612,6 @@
(get-theme-tree [_] "get a nested tree of all themes in the library")
(get-themes [_] "get an ordered sequence of all themes in the library")
(get-theme [_ group name] "get one theme looking for name")
(get-hidden-theme [_] "get the theme hidden from the user, used for managing active sets without a user created theme.")
(get-theme-groups [_] "get a sequence of group names by order")
(get-active-theme-paths [_] "get the active theme paths")
(get-active-themes [_] "get an ordered sequence of active themes in the library")
@ -946,14 +945,21 @@ Will return a value that matches this schema:
(let [prefixed-from-path (set-full-path->set-prefixed-full-path from-path)
prev-set (get-in sets prefixed-from-path)]
(if (instance? TokenSet prev-set)
(let [prefixed-to-path (set-full-path->set-prefixed-full-path to-path)
prefixed-before-path (when before-path
(let [prefixed-to-path
(set-full-path->set-prefixed-full-path to-path)
prefixed-before-path
(when before-path
(if before-group?
(mapv add-set-path-group-prefix before-path)
(set-full-path->set-prefixed-full-path before-path)))
set (assoc prev-set :name (join-set-path to-path))
reorder? (= prefixed-from-path prefixed-to-path)
set
(assoc prev-set :name (join-set-path to-path))
reorder?
(= prefixed-from-path prefixed-to-path)
sets'
(if reorder?
(d/oreorder-before sets
@ -965,6 +971,7 @@ Will return a value that matches this schema:
(d/oassoc-in-before sets prefixed-before-path prefixed-to-path set)
(d/oassoc-in sets prefixed-to-path set))
(d/dissoc-in prefixed-from-path)))]
(TokensLib. sets'
(if reorder?
themes
@ -1130,9 +1137,6 @@ Will return a value that matches this schema:
(get-theme [_ group name]
(dm/get-in themes [group name]))
(get-hidden-theme [this]
(get-theme this hidden-token-theme-group hidden-token-theme-name))
(set-active-themes [_ active-themes]
(TokensLib. sets
themes
@ -1368,6 +1372,10 @@ Will return a value that matches this schema:
(valid-token-themes? themes)
(valid-active-token-themes? active-themes))))
(defn get-hidden-theme
[tokens-lib]
(get-theme tokens-lib hidden-token-theme-group hidden-token-theme-name))
(defn valid-tokens-lib?
[o]
(and (instance? TokensLib o)
@ -1382,27 +1390,32 @@ Will return a value that matches this schema:
(def ^:private check-active-themes
(sm/check-fn schema:active-themes :hint "expected valid active themes"))
(defn make-tokens-lib
"Create an empty or prepopulated tokens library."
([]
(defn- ensure-hidden-theme
"A helper that is responsible to ensure that the hidden theme always
exists on the themes data structure"
[themes]
(update themes hidden-token-theme-group
(fn [data]
(if (contains? data hidden-token-theme-name)
data
(d/oassoc data hidden-token-theme-name (make-hidden-token-theme))))))
;; NOTE: is possible that ordered map is not the most apropriate
;; data structure and maybe we need a specific that allows us an
;; easy way to reorder it, or just store inside Tokens data
;; structure the data and the order separately as we already do
;; with pages and pages-index.
(make-tokens-lib :sets (d/ordered-map)
:themes (d/ordered-map)
:active-themes #{hidden-token-theme-path}))
([& {:keys [sets themes active-themes]}]
(let [active-themes (d/nilv active-themes #{hidden-token-theme-path})
themes (if (empty? themes)
(update themes hidden-token-theme-group d/oassoc hidden-token-theme-name (make-hidden-token-theme))
themes)]
(defn make-tokens-lib
"Create an empty or prepopulated tokens library."
[& {:keys [sets themes active-themes]}]
(let [sets (or sets (d/ordered-map))
themes (-> (or themes (d/ordered-map))
(ensure-hidden-theme))
active-themes (or active-themes #{hidden-token-theme-path})]
(TokensLib.
(check-token-sets sets)
(check-token-themes themes)
(check-active-themes active-themes)))))
(check-active-themes active-themes))))
(defn ensure-tokens-lib
[tokens-lib]
@ -1445,6 +1458,71 @@ Will return a value that matches this schema:
:wfn #(into {} %)
:rfn #(map->Token %)})
#?(:clj
(defn- read-tokens-lib-v1-0
"Reads the first version of tokens lib, now completly obsolete"
[r]
(let [;; Migrate sets tree without prefix to new format
prev-sets (->> (fres/read-object! r)
(tree-seq d/ordered-map? vals)
(filter (partial instance? TokenSet)))
sets (-> (reduce add-set (make-tokens-lib) prev-sets)
(deref)
(:sets))
_set-groups (fres/read-object! r)
themes (fres/read-object! r)
active-themes (fres/read-object! r)]
(->TokensLib sets themes active-themes))))
#?(:clj
(defn- read-tokens-lib-v1-1
"Reads the tokens lib data structure and ensures that hidden
theme exists and adds missing ID on themes"
[r]
(let [sets (fres/read-object! r)
themes (fres/read-object! r)
active-themes (fres/read-object! r)
;; Ensure we have at least a hidden theme
themes
(ensure-hidden-theme themes)
;; Ensure we add an :id field for each existing theme
themes
(reduce (fn [result group-id]
(update result group-id
(fn [themes]
(reduce (fn [themes theme-id]
(update themes theme-id
(fn [theme]
(if (get theme :id)
theme
(assoc theme :id (str (uuid/next)))))))
themes
(keys themes)))))
themes
(keys themes))]
(->TokensLib sets themes active-themes))))
#?(:clj
(defn- write-tokens-lib
[n w ^TokensLib o]
(fres/write-tag! w n 3)
(fres/write-object! w (.-sets o))
(fres/write-object! w (.-themes o))
(fres/write-object! w (.-active-themes o))))
#?(:clj
(defn- read-tokens-lib
[r]
(let [sets (fres/read-object! r)
themes (fres/read-object! r)
active-themes (fres/read-object! r)]
(->TokensLib sets themes active-themes))))
#?(:clj
(fres/add-handlers!
{:name "penpot/token/v1"
@ -1474,32 +1552,15 @@ Will return a value that matches this schema:
(let [obj (fres/read-object! r)]
(map->TokenTheme obj)))}
;; LEGACY TOKENS LIB READERS (with migrations)
{:name "penpot/tokens-lib/v1"
:rfn (fn [r]
(let [;; Migrate sets tree without prefix to new format
prev-sets (->> (fres/read-object! r)
(tree-seq d/ordered-map? vals)
(filter (partial instance? TokenSet)))
;; FIXME: wtf we usind deref here?
sets (-> (reduce add-set (make-tokens-lib) prev-sets)
(deref)
(:sets))
_set-groups (fres/read-object! r)
themes (fres/read-object! r)
active-themes (fres/read-object! r)]
(->TokensLib sets themes active-themes)))}
:rfn read-tokens-lib-v1-0}
{:name "penpot/tokens-lib/v1.1"
:rfn read-tokens-lib-v1-1}
;; CURRENT TOKENS LIB READER & WRITTER
{:name "penpot/tokens-lib/v1.2"
:class TokensLib
:wfn (fn [n w o]
(fres/write-tag! w n 3)
(fres/write-object! w (.-sets o))
(fres/write-object! w (.-themes o))
(fres/write-object! w (.-active-themes o)))
:rfn (fn [r]
(let [sets (fres/read-object! r)
themes (fres/read-object! r)
active-themes (fres/read-object! r)]
(->TokensLib sets themes active-themes)))}))
:wfn write-tokens-lib
:rfn read-tokens-lib}))

View file

@ -84,8 +84,7 @@
(t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception) #"expected valid params for token-set"
(ctob/make-token-set params)))))
(t/deftest move-token-set
(t/testing "flat"
(t/deftest move-token-set-flat
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "A"))
(ctob/add-set (ctob/make-token-set :name "B"))
@ -103,7 +102,7 @@
(t/testing "move to bottom"
(t/is (= ["A" "B" "Move"] (move ["Move"] ["Move"] nil false))))))
(t/testing "nested"
(t/deftest move-token-set-nested
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "Foo/Baz"))
(ctob/add-set (ctob/make-token-set :name "Foo/Bar"))
@ -121,15 +120,29 @@
(t/is (= ["Foo/Foo" "Foo/Baz" "Foo/Bar"] (move ["Foo"] ["Foo" "Foo"] ["Foo" "Baz"] false)))
(t/is (= ["Foo/Baz" "Foo/Bar" "Foo/Foo"] (move ["Foo"] ["Foo" "Foo"] nil false))))))
;; FIXME
(t/testing "updates theme set names"
(t/deftest move-token-set-nested-2
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "a/b"))
(ctob/add-set (ctob/make-token-set :name "a/a"))
(ctob/add-set (ctob/make-token-set :name "b/a"))
(ctob/add-set (ctob/make-token-set :name "b/b")))
move (fn [from-path to-path before-path before-group?]
(->> (ctob/move-set tokens-lib from-path to-path before-path before-group?)
(ctob/get-ordered-set-names)
(vec)))]
(t/testing "move within group"
(t/is (= ["a/b" "a/a" "b/a" "b/b"] (vec (ctob/get-ordered-set-names tokens-lib))))
(t/is (= ["a/a" "a/b" "b/a" "b/b"] (move ["a" "b"] ["a" "b"] nil true))))))
(t/deftest move-token-set-nested-3
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "Foo/Bar/Baz"))
(ctob/add-set (ctob/make-token-set :name "Other"))
(ctob/add-theme (ctob/make-token-theme :name "Theme"
:sets #{"Foo/Bar/Baz"}))
(ctob/move-set ["Foo" "Bar" "Baz"] ["Other/Baz"] nil nil))]
(t/is (= #{"Other/Baz"} (:sets (ctob/get-theme tokens-lib "" "Theme")))))))
(t/is (= #{"Other/Baz"} (:sets (ctob/get-theme tokens-lib "" "Theme"))))))
(t/deftest move-token-set-group
(t/testing "reordering"
@ -213,7 +226,7 @@
(t/is (= (ctob/set-count tokens-lib) 0))))
(t/deftest make-invalid-tokens-lib
(let [params {:sets nil :themes nil}]
(let [params {:sets {} :themes {}}]
(t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception) #"expected valid token sets"
(ctob/make-tokens-lib params)))))

View file

@ -2,6 +2,14 @@
title: 1.3 Install with Docker
---
<p class="advice">
Installing and maintaining a self-hosted Penpot instance requires some technical knowledge:
Docker and Docker Compose, basic DNS management, and proxy configuration.
If you're not comfortable with this stack, we encourage you to try
more straight-forward installations with <a href="https://help.penpot.app/technical-guide/getting-started/elestio/" target="_blank">Elestio</a>
or use the SAAS at <a href="https://design.penpot.app" targret="_blank">https://design.penpot.app</a>.
</p>
# Install with Docker
This section details everything you need to know to get Penpot up and running in
@ -10,42 +18,10 @@ production environments using Docker. For this, we provide a series of *Dockerfi
## Install Docker
<p class="advice">
Skip this section if you already have docker installed, up and running.
</p>
Currently, Docker comes into two different flavours:
### Docker Desktop
This is the only option to have Docker in a Windows or MacOS. Recently it's also available
for Linux, in the most popular distributions (Debian, Ubuntu and Fedora).
You can install it following the <a href="https://docs.docker.com/desktop/"
target="_blank">official guide</a>.
Docker Desktop has a graphical control panel (GUI) to manage the service and view the
containers, images and volumes. But you need the command line (Terminal in Linux and Mac, or
PowerShell in Windows) to build and run the containers, and execute other operations.
It already includes **docker compose** utility, needed by Penpot.
### Docker Engine
This is the classic and default Docker setup for Linux machines, and the only option for a
Linux VPS without graphical interface.
You can install it following the <a href="https://docs.docker.com/engine/"
target="_blank">official guide</a>.
And you also need the [docker
compose](https://docs.docker.com/compose/cli-command/#installing-compose-v2) (V2)
plugin. You can use the old **docker-compose** tool, but all the documentation supposes
you are using the V2.
You can easily check which version of **docker compose** you have. If you can execute
<code class="language-bash">docker compose</code> command, then you have V2. If you need to write <code class="language-bash">docker-compose</code> (with a
<code class="language-bash">-</code>) for it to work, you have the old version.
To host a Penpot instance with Docker, it's necessary to have
<code class="language-bash">docker</code> and <code class="language-bash">docker compose</code>
installed. Check the comprehensive <a href="https://docs.docker.com/" target="_blank">official documentation</a>
to install and maintain docker.
## Start Penpot

View file

@ -648,9 +648,7 @@
(defn detach-comment-thread
"Detach comment threads that are inside a frame when that frame is deleted"
[ids]
(dm/assert!
"expected a valid coll of uuid's"
(sm/check-coll-of-uuid! ids))
(assert (sm/check-coll-of-uuid ids))
(ptk/reify ::detach-comment-thread
ptk/WatchEvent

View file

@ -536,11 +536,8 @@
(defn move-files
[{:keys [ids project-id] :as params}]
(dm/assert! (uuid? project-id))
(dm/assert!
"expected a valid set of uuids"
(sm/check-set-of-uuid! ids))
(assert (uuid? project-id))
(assert (sm/check-set-of-uuid ids))
(ptk/reify ::move-files
ev/Event

View file

@ -350,12 +350,10 @@
(defn create-invitations
[{:keys [emails role team-id resend?] :as params}]
(dm/assert! (keyword? role))
(dm/assert! (uuid? team-id))
(dm/assert!
"expected a valid set of emails"
(sm/check-set-of-emails! emails))
(assert (keyword? role))
(assert (uuid? team-id))
(assert (sm/check-set-of-emails emails))
(ptk/reify ::create-invitations
ev/Event
@ -376,11 +374,8 @@
(defn copy-invitation-link
[{:keys [email team-id] :as params}]
(dm/assert!
"expected a valid email"
(sm/check-email! email))
(dm/assert! (uuid? team-id))
(assert (sm/check-email email))
(assert (uuid? team-id))
(ptk/reify ::copy-invitation-link
IDeref
@ -406,12 +401,9 @@
(defn update-invitation-role
[{:keys [email team-id role] :as params}]
(dm/assert!
"expected a valid email"
(sm/check-email! email))
(dm/assert! (uuid? team-id))
(dm/assert! (contains? ctt/valid-roles role))
(assert (sm/check-email email))
(assert (uuid? team-id))
(assert (contains? ctt/valid-roles role))
(ptk/reify ::update-invitation-role
IDeref
@ -428,8 +420,9 @@
(defn delete-invitation
[{:keys [email team-id] :as params}]
(dm/assert! (sm/check-email! email))
(dm/assert! (uuid? team-id))
(assert (sm/check-email email))
(assert (uuid? team-id))
(ptk/reify ::delete-invitation
ptk/WatchEvent
(watch [_ _ _]

View file

@ -252,6 +252,8 @@
:level :error
:timeout 9000})))))))
;; FIXME: add schema for params
(defn drop-token-set-group [drop-opts]
(ptk/reify ::drop-token-set-group
ptk/WatchEvent
@ -265,17 +267,21 @@
(rx/of
(drop-error (ex-data e))))))))
(defn drop-token-set [drop-opts]
;; FIXME: add schema for params
(defn drop-token-set
[params]
(ptk/reify ::drop-token-set
ptk/WatchEvent
(watch [it state _]
(try
(when-let [changes (clt/generate-move-token-set (pcb/empty-changes it) (get-tokens-lib state) drop-opts)]
(let [tokens-lib (get-tokens-lib state)
changes (-> (pcb/empty-changes it)
(clt/generate-move-token-set tokens-lib params))]
(rx/of (dch/commit-changes changes)
(wtu/update-workspace-tokens)))
(catch :default e
(rx/of
(drop-error (ex-data e))))))))
(catch :default cause
(rx/of (drop-error (ex-data cause))))))))
(defn- create-token-with-set
"A special case when a first token is created and no set exists"

View file

@ -406,6 +406,7 @@
:workspace-media-objects
:workspace-persistence
:workspace-presence
:workspace-tokens
:workspace-undo)
(update :workspace-global dissoc :read-only?)
(assoc-in [:workspace-global :options-mode] :design)))

View file

@ -134,9 +134,7 @@
;; Move comment threads that are inside a frame when that frame is moved"
(defmethod ptk/resolve ::move-frame-comment-threads
[_ ids]
(dm/assert!
"expected a valid coll of uuid's"
(sm/check-coll-of-uuid! ids))
(assert (sm/check-coll-of-uuid ids))
(ptk/reify ::move-frame-comment-threads
ptk/WatchEvent

View file

@ -120,6 +120,14 @@
(pcb/with-page-id page-id)
(pcb/with-objects objects)
(pcb/add-object group {:index group-idx})
;; Create a group needs to reset the constraints to scale/scale
(pcb/update-shapes
(map :id shapes)
(fn [shape]
(-> shape
(d/assoc-when :constraints-h :scale)
(d/assoc-when :constraints-v :scale))))
(pcb/update-shapes (map :id shapes) ctl/remove-layout-item-data)
(pcb/change-parent (:id group) (reverse shapes))
(pcb/update-shapes (map :id shapes-to-detach) ctk/detach-shape)

View file

@ -533,69 +533,11 @@
(assoc state :workspace-modifiers modif-tree)))))
(defn apply-modifiers
([]
(apply-modifiers nil))
(def ^:private xf:without-uuid-zero
(remove #(= % uuid/zero)))
([{:keys [modifiers undo-transation? stack-undo? ignore-constraints
ignore-snap-pixel ignore-touched undo-group page-id]
:or {undo-transation? true stack-undo? false ignore-constraints false
ignore-snap-pixel false ignore-touched false}}]
(ptk/reify ::apply-modifiers
ptk/WatchEvent
(watch [_ state _]
(let [text-modifiers (get state :workspace-text-modifier)
page-id (or page-id (:current-page-id state))
objects (dsh/lookup-page-objects state page-id)
object-modifiers
(if (some? modifiers)
(calculate-modifiers state ignore-constraints ignore-snap-pixel modifiers page-id)
(get state :workspace-modifiers))
ids
(into []
(remove #(= % uuid/zero))
(keys object-modifiers))
ids-with-children
(into ids
(mapcat (partial cfh/get-children-ids objects))
ids)
ignore-tree
(calculate-ignore-tree object-modifiers objects)
undo-id (js/Symbol)]
(rx/concat
(if undo-transation?
(rx/of (dwu/start-undo-transaction undo-id))
(rx/empty))
(rx/of (ptk/event ::dwg/move-frame-guides {:ids ids-with-children :modifiers object-modifiers})
(ptk/event ::dwcm/move-frame-comment-threads ids-with-children)
(dwsh/update-shapes
ids
(fn [shape]
(let [modif (get-in object-modifiers [(:id shape) :modifiers])
text-shape? (cfh/text-shape? shape)
position-data (when text-shape?
(dm/get-in text-modifiers [(:id shape) :position-data]))]
(-> shape
(gsh/transform-shape modif)
(cond-> (d/not-empty? position-data)
(assoc-position-data position-data shape))
(cond-> text-shape?
(update-grow-type shape)))))
{:reg-objects? true
:stack-undo? stack-undo?
:ignore-tree ignore-tree
:ignore-touched ignore-touched
:undo-group undo-group
:page-id page-id
;; Attributes that can change in the transform. This way we don't have to check
;; all the attributes
:attrs [:selrect
(def ^:private transform-attrs
#{:selrect
:points
:x
:y
@ -619,22 +561,93 @@
:layout-gap
:layout-padding
:layout-item-h-sizing
:layout-item-margin
:layout-item-max-h
:layout-item-max-w
:layout-item-min-h
:layout-item-min-w
:layout-item-v-sizing
:layout-padding-type
:layout-gap
:layout-item-margin
:layout-item-margin-type
:layout-grid-cells
:layout-grid-columns
:layout-grid-rows]})
;; We've applied the text-modifier so we can dissoc the temporary data
:layout-grid-rows})
(defn apply-modifiers*
"A lower-level version of apply-modifiers, that expects receive ready
to use objects, object-modifiers and text-modifiers."
[objects object-modifiers text-modifiers options]
(ptk/reify ::apply-modifiers*
ptk/WatchEvent
(watch [_ _ _]
(let [ids
(into [] xf:without-uuid-zero (keys object-modifiers))
ids-with-children
(into ids
(mapcat (partial cfh/get-children-ids objects))
ids)
ignore-tree
(calculate-ignore-tree object-modifiers objects)
options
(-> options
(assoc :reg-objects? true)
(assoc :ignore-tree ignore-tree)
;; Attributes that can change in the transform. This
;; way we don't have to check all the attributes
(assoc :attrs transform-attrs))
update-shape
(fn [shape]
(let [shape-id (dm/get-prop shape :id)
modifiers (dm/get-in object-modifiers [shape-id :modifiers])
text-shape? (cfh/text-shape? shape)
pos-data (when ^boolean text-shape?
(dm/get-in text-modifiers [shape-id :position-data]))]
(-> shape
(gsh/transform-shape modifiers)
(cond-> (d/not-empty? pos-data)
(assoc-position-data pos-data shape))
(cond-> text-shape?
(update-grow-type shape)))))]
(rx/of (ptk/event ::dwg/move-frame-guides {:ids ids-with-children :modifiers object-modifiers})
(ptk/event ::dwcm/move-frame-comment-threads ids-with-children)
(dwsh/update-shapes ids update-shape options))))))
(defn apply-modifiers
([]
(apply-modifiers nil))
([{:keys [modifiers undo-transation? ignore-constraints
ignore-snap-pixel page-id]
:or {undo-transation? true ignore-constraints false
ignore-snap-pixel false}
:as options}]
(ptk/reify ::apply-modifiers
ptk/WatchEvent
(watch [_ state _]
(let [text-modifiers (get state :workspace-text-modifier)
page-id (or page-id (:current-page-id state))
objects (dsh/lookup-page-objects state page-id)
object-modifiers
(if (some? modifiers)
(calculate-modifiers state ignore-constraints ignore-snap-pixel modifiers page-id)
(get state :workspace-modifiers))
undo-id
(js/Symbol)]
(rx/concat
(if undo-transation?
(rx/of (dwu/start-undo-transaction undo-id))
(rx/empty))
(rx/of (apply-modifiers* objects object-modifiers text-modifiers options)
(fn [state]
(update state :workspace-text-modifier #(apply dissoc % ids))))
(let [ids (into [] xf:without-uuid-zero (keys object-modifiers))]
(update state :workspace-text-modifier #(apply dissoc % ids)))))
(if (nil? modifiers)
(rx/of (clear-local-transform))
(rx/empty))

View file

@ -51,11 +51,8 @@
([ids update-fn {:keys [reg-objects? save-undo? stack-undo? attrs ignore-tree page-id ignore-touched undo-group with-objects? changed-sub-attr]
:or {reg-objects? false save-undo? true stack-undo? false ignore-touched false with-objects? false}}]
(dm/assert!
"expected a valid coll of uuid's"
(sm/check-coll-of-uuid! ids))
(dm/assert! (fn? update-fn))
(assert (sm/check-coll-of-uuid ids))
(assert (fn? update-fn))
(ptk/reify ::update-shapes
ptk/WatchEvent
@ -162,9 +159,7 @@
([ids] (delete-shapes nil ids {}))
([page-id ids] (delete-shapes page-id ids {}))
([page-id ids options]
(dm/assert!
"expected a valid set of uuid's"
(sm/check-set-of-uuid! ids))
(assert (sm/check-set-of-uuid ids))
(ptk/reify ::delete-shapes
ptk/WatchEvent

View file

@ -340,35 +340,35 @@
(rx/filter (ptk/type? ::trigger-bounding-box-cloaking) stream)))))))
(defn update-dimensions
"Change size of shapes, from the sideber options form.
Will ignore pixel snap used in the options side panel"
"Change size of shapes, from the sidebar options form
(will ignore pixel snap)"
([ids attr value] (update-dimensions ids attr value nil))
([ids attr value options]
(dm/assert! (number? value))
(dm/assert!
"expected valid coll of uuids"
(every? uuid? ids))
(dm/assert!
"expected valid attr"
(contains? #{:width :height} attr))
(assert (number? value))
(assert (every? uuid? ids)
"expected valid coll of uuids")
(assert (contains? #{:width :height} attr)
"expected valid attr")
(ptk/reify ::update-dimensions
ptk/UpdateEvent
(update [_ state]
(let [page-id (or (get options :page-id)
ptk/WatchEvent
(watch [_ state _]
(let [page-id
(or (get options :page-id)
(get state :current-page-id))
objects (dsh/lookup-page-objects state page-id)
objects
(dsh/lookup-page-objects state page-id)
get-modifier
(fn [shape] (ctm/change-dimensions-modifiers shape attr value))
(fn [shape]
(ctm/change-dimensions-modifiers shape attr value))
modif-tree
(-> (dwm/build-modif-tree ids objects get-modifier)
(gm/set-objects-modifiers objects))]
(assoc state :workspace-modifiers modif-tree)))
ptk/WatchEvent
(watch [_ _ _]
(rx/of (dwm/apply-modifiers options))))))
(rx/of (dwm/apply-modifiers* objects modif-tree nil options)))))))
(defn change-orientation
"Change orientation of shapes, from the sidebar options form.
@ -859,27 +859,27 @@
(rx/of (reorder-selected-layout-child direction))
(rx/of (nudge-selected-shapes direction shift?)))))))
(defn- get-delta [position bbox]
(let [cpos (gpt/point (:x bbox) (:y bbox))
pos (gpt/point (or (:x position) (:x bbox))
(or (:y position) (:y bbox)))]
(gpt/subtract pos cpos)))
(defn- get-relative-delta [position bbox frame]
(let [frame-bbox (-> frame :points grc/points->rect)
relative-cpos (gpt/subtract (gpt/point (:x bbox) (:y bbox))
(gpt/point (:x frame-bbox)
(:y frame-bbox)))
cpos (gpt/point (:x relative-cpos) (:y relative-cpos))
pos (gpt/point (or (:x position) (:x relative-cpos))
(or (:y position) (:y relative-cpos)))]
(gpt/subtract pos cpos)))
(defn- calculate-delta
[position bbox relative-to]
(let [current (gpt/point (:x bbox) (:y bbox))
position (gpt/point (or (some-> (:x position) (+ (dm/get-prop relative-to :x)))
(:x bbox))
(or (some-> (:y position) (+ (dm/get-prop relative-to :y)))
(:y bbox)))]
(gpt/subtract position current)))
(defn update-position
"Move shapes to a new position"
"Move shapes to a new position. It will resolve to the current frame
of the shape, unless given the absolute option. In this case it will
resolve to the root frame of the page.
The position is a map that can have a partial position (it means it
can receive {:x 10}."
([id position] (update-position id position nil))
([id position options]
(dm/assert! (uuid? id))
(assert (uuid? id) "expected a valid uuid for `id`")
(assert (map? position) "expected a valid map for `position`")
(ptk/reify ::update-position
ptk/WatchEvent
(watch [_ state _]
@ -887,14 +887,16 @@
(get state :current-page-id))
objects (dsh/lookup-page-objects state page-id)
shape (get objects id)
;; FIXME: performance rect
bbox (-> shape :points grc/points->rect)
frame (cfh/get-frame objects shape)
delta (if (:absolute? options)
(get-delta position bbox)
(get-relative-delta position bbox frame))
modif-tree (dwm/create-modif-tree [id] (ctm/move-modifiers delta))]
(rx/of (dwm/apply-modifiers {:modifiers modif-tree
frame (if (:absolute? options)
(cfh/get-frame objects)
(cfh/get-parent-frame objects shape))
delta (calculate-delta position bbox frame)
modifiers (dwm/create-modif-tree [id] (ctm/move-modifiers delta))]
(rx/of (dwm/apply-modifiers {:modifiers modifiers
:page-id page-id
:ignore-constraints false
:ignore-touched (:ignore-touched options)

View file

@ -22,7 +22,8 @@
;; Change this to :info :debug or :trace to debug this module
(log/set-level! :warn)
(def discard-transaction-time-millis (* 20 1000))
(def ^:private
discard-transaction-time-millis (* 20 1000))
(def ^:private
schema:undo-entry
@ -30,7 +31,7 @@
[:undo-changes [:vector ::cpc/change]]
[:redo-changes [:vector ::cpc/change]]])
(def check-undo-entry!
(def check-undo-entry
(sm/check-fn schema:undo-entry))
(def MAX-UNDO-SIZE 50)
@ -48,8 +49,7 @@
(ptk/reify ::materialize-undo
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-undo :index] index)))))
(update state :workspace-undo assoc :index index))))
(defn- add-undo-entry
[state entry]
@ -88,12 +88,9 @@
(defn append-undo
[entry stack?]
(dm/assert!
"expected valid undo entry"
(check-undo-entry! entry))
(dm/assert!
(boolean? stack?))
(assert (check-undo-entry entry))
(assert (boolean? stack?))
(ptk/reify ::append-undo
ptk/UpdateEvent
@ -118,17 +115,11 @@
(defn start-undo-transaction
"Start a transaction, so that every changes inside are added together in a single undo entry."
[id]
[id & {:keys [timeout] :or {timeout discard-transaction-time-millis}}]
(ptk/reify ::start-undo-transaction
ptk/WatchEvent
(watch [_ _ _]
(->> (rx/of (check-open-transactions))
;; Wait the configured time
(rx/delay discard-transaction-time-millis)))
ptk/UpdateEvent
(update [_ state]
(log/info :msg "start-undo-transaction")
(log/info :hint "start-undo-transaction")
;; We commit the old transaction before starting the new one
(let [current-tx (get-in state [:workspace-undo :transaction])
pending-tx (get-in state [:workspace-undo :transactions-pending])]
@ -136,20 +127,28 @@
(nil? current-tx) (assoc-in [:workspace-undo :transaction] empty-tx)
(nil? pending-tx) (assoc-in [:workspace-undo :transactions-pending] #{id})
(some? pending-tx) (update-in [:workspace-undo :transactions-pending] conj id)
:always (update-in [:workspace-undo :transactions-pending-ts] assoc id (dt/now)))))))
:always (update-in [:workspace-undo :transactions-pending-ts] assoc id (dt/now)))))
ptk/WatchEvent
(watch [_ _ _]
(when (and timeout (pos? timeout))
(->> (rx/of (check-open-transactions timeout))
;; Wait the configured time
(rx/delay timeout))))))
(defn discard-undo-transaction []
(ptk/reify ::discard-undo-transaction
ptk/UpdateEvent
(update [_ state]
(log/info :msg "discard-undo-transaction")
(log/info :hint "discard-undo-transaction")
(update state :workspace-undo dissoc :transaction :transactions-pending :transactions-pending-ts))))
(defn commit-undo-transaction [id]
(ptk/reify ::commit-undo-transaction
ptk/UpdateEvent
(update [_ state]
(log/info :msg "commit-undo-transaction")
(log/info :hint "commit-undo-transaction")
(let [state (-> state
(update-in [:workspace-undo :transactions-pending] disj id)
(update-in [:workspace-undo :transactions-pending-ts] dissoc id))]
@ -166,15 +165,15 @@
(assoc state :workspace-undo {}))))
(defn check-open-transactions
[]
[timeout]
(ptk/reify ::check-open-transactions
ptk/WatchEvent
(watch [_ state _]
(log/info :msg "check-open-transactions")
(log/info :hint "check-open-transactions" :timeout timeout)
(let [pending-ts (-> (dm/get-in state [:workspace-undo :transactions-pending-ts])
(update-vals #(.toMillis (dt/diff (dt/now) %))))]
(update-vals #(inst-ms (dt/diff (dt/now) %))))]
(->> pending-ts
(filter (fn [[_ ts]] (>= ts discard-transaction-time-millis)))
(filter (fn [[_ ts]] (>= ts timeout)))
(rx/from)
(rx/tap #(js/console.warn (dm/str "FORCE COMMIT TRANSACTION AFTER " (second %) "MS")))
(rx/map first)

View file

@ -112,16 +112,16 @@
(dom/stop-propagation event)
(on-import item event))))]
[:a {:class (stl/css :card-container)
[:div {:class (stl/css :card-container)
:tab-index (if (or (not is-visible) collapsed) "-1" "0")
:id id
:data-index index
:data-index index}
[:a {:class (stl/css :template-card)
:on-click on-click
:on-mouse-down dom/prevent-default
:on-mouse-enter #(reset! hover? true)
:on-mouse-leave #(reset! hover? false)
:on-key-down on-key-down}
[:div {:class (stl/css :template-card)}
[:div {:class (stl/css :img-container)}
[:img {:src (dm/str thb)
:alt (:name item)

View file

@ -205,10 +205,10 @@
;; ORIENTATION
orientation (when (= type :frame)
(cond (> (:width values) (:height values))
orientation
(when (= type :frame)
(if (> (:width values) (:height values))
:horiz
:else
:vert))
on-orientation-change
@ -235,10 +235,8 @@
(run! #(st/emit! (udw/set-shape-proportion-lock % new-lock)) ids))))
;; POSITION
do-position-change
(mf/use-fn
(mf/deps ids)
(fn [shape' value attr]
(st/emit! (udw/update-position (:id shape') {attr value}))))
@ -248,7 +246,7 @@
(fn [value attr]
(st/emit! (udw/trigger-bounding-box-cloaking ids))
(binding [cts/*wasm-sync* true]
(doall (map #(do-position-change %1 value attr) shapes)))))
(run! #(do-position-change %1 value attr) shapes))))
;; ROTATION

View file

@ -304,13 +304,11 @@
ptk/WatchEvent
(watch [_ state _]
(when (number? value)
(let [page-id' (or page-id (get state :current-page-id))]
(rx/concat
(map #(dwt/update-position % (zipmap attributes (repeat value))
(let [page-id (or page-id (get state :current-page-id))]
(->> (rx/from shape-ids)
(rx/map #(dwt/update-position % (zipmap attributes (repeat value))
{:ignore-touched true
:page-id page-id'})
shape-ids))))))))
:page-id page-id})))))))))
(defn update-layout-sizing-limits
([value shape-ids attributes] (update-layout-sizing-limits value shape-ids attributes nil))

View file

@ -140,24 +140,18 @@
[:div {:class (stl/css :theme-row-right)}
(if-let [sets-count (some-> theme :sets seq count)]
[:> button* {:class (stl/css :sets-count-button)
(let [sets-count (some-> theme :sets seq count)]
[:> button* {:class (stl/css-case :sets-count-button sets-count
:sets-count-empty-button (not sets-count))
:variant "secondary"
:type "button"
:title (tr "workspace.token.sets-hint")
:on-click on-edit-theme}
[:div {:class (stl/css :label-wrapper)}
[:> text* {:as "span" :typography "body-medium"}
(tr "workspace.token.num-active-sets" sets-count)]
[:> icon* {:icon-id "arrow-right"}]]]
[:> button* {:class (stl/css :sets-count-empty-button)
:type "button"
:variant "secondary"
:on-click on-edit-theme}
[:div {:class (stl/css :label-wrapper)}
[:> text* {:as "span" :typography "body-medium"}
(tr "workspace.token.no-active-sets")]
(if sets-count
(tr "workspace.token.num-active-sets" sets-count)
(tr "workspace.token.no-active-sets"))]
[:> icon* {:icon-id "arrow-right"}]]])
[:> icon-button* {:on-click delete-theme

View file

@ -368,13 +368,13 @@
(mf/use-fn
(mf/deps collapsed-paths)
(fn [tree-index position data]
(let [props {:from-index (:index data)
(let [params {:from-index (:index data)
:to-index tree-index
:position position
:collapsed-paths collapsed-paths}]
(if (:is-group data)
(st/emit! (dt/drop-token-set-group props))
(st/emit! (dt/drop-token-set props))))))
(st/emit! (dt/drop-token-set-group params))
(st/emit! (dt/drop-token-set params))))))
on-toggle-collapse
(mf/use-fn

View file

@ -103,8 +103,9 @@
(defn- generate-tooltip
"Generates a tooltip for a given token"
[is-viewer shape theme-token token half-applied no-valid-value ref-not-in-active-set]
(let [{:keys [name value type]} token
{:keys [resolved-value]} theme-token
(let [{:keys [name value type resolved-value]} token
resolved-value-theme (:resolved-value theme-token)
resolved-value (or resolved-value-theme resolved-value)
{:keys [title] :as token-props} (wtch/get-token-properties theme-token)
applied-tokens (:applied-tokens shape)
app-token-vals (set (vals applied-tokens))

View file

@ -1,6 +1,13 @@
;; 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.ui.workspace.tokens.update
(:require
[app.common.files.helpers :as cfh]
[app.common.logging :as l]
[app.common.types.token :as ctt]
[app.main.data.helpers :as dsh]
[app.main.data.workspace.shapes :as dwsh]
@ -9,6 +16,7 @@
[app.main.ui.workspace.tokens.changes :as wtch]
[app.main.ui.workspace.tokens.style-dictionary :as wtsd]
[app.main.ui.workspace.tokens.token-set :as wtts]
[app.util.time :as dt]
[beicon.v2.core :as rx]
[clojure.data :as data]
[clojure.set :as set]
@ -70,7 +78,7 @@
(reduce
(fn [acc [attrs v]]
(cond
(some attrs #{:widht :height}) (let [[_ a b] (data/diff #{:width :height} attrs)]
(some attrs #{:width :height}) (let [[_ a b] (data/diff #{:width :height} attrs)]
(cond-> (assoc acc b v)
;; Exact match in attrs
a (assoc a v)))
@ -127,8 +135,14 @@
[state resolved-tokens]
(let [file-id (get state :current-file-id)
current-page-id (get state :current-page-id)
fdata (dsh/lookup-file-data state file-id)]
fdata (dsh/lookup-file-data state file-id)
tpoint (dt/tpoint-ms)]
(l/inf :status "START" :hint "update-tokens")
(->> (rx/concat
(rx/of current-page-id)
(->> (rx/from (:pages fdata))
(rx/filter (fn [id] (not= id current-page-id)))))
(rx/mapcat
(fn [page-id]
(let [page
@ -140,6 +154,12 @@
actions
(actionize-shapes-update-info page-id attrs)]
(l/inf :status "PROGRESS"
:hint "update-tokens"
:page-id (str page-id)
:elapsed (tpoint)
::l/sync? true)
(rx/merge
(rx/from actions)
(->> (rx/from frame-ids)
@ -151,7 +171,11 @@
(fn [shape]
(dissoc shape :position-data))
{:page-id page-id
:ignore-touched true}))))))))))
:ignore-touched true})))))))
(rx/finalize
(fn [_]
(let [elapsed (tpoint)]
(l/inf :status "END" :hint "update-tokens" :elapsed elapsed)))))))
(defn update-workspace-tokens
[]
@ -164,6 +188,6 @@
(rx/mapcat (fn [sd-tokens]
(let [undo-id (js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id))
(rx/of (dwu/start-undo-transaction undo-id :timeout false))
(update-tokens state sd-tokens)
(rx/of (dwu/commit-undo-transaction undo-id)))))))))))

View file

@ -176,8 +176,9 @@
(defn format-shadows
[shadows]
(when (some? shadows)
(format-array format-shadow shadows)))
(if (some? shadows)
(format-array format-shadow shadows)
(array)))
;;export interface Fill {
;; fillColor?: string;

View file

@ -6738,7 +6738,7 @@ msgstr "%s sets activos"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs:147
msgid "workspace.token.sets-hint"
msgstr "Editar tema y gestionar set"
msgstr "Editar tema y gestionar sets"
#: src/app/main/ui/workspace/tokens/token_pill.cljs:114
#, fuzzy