Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d8f0bb125 | ||
|
|
ea8a7dd3d8 | ||
|
|
65b578a549 |
No files matched your search
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.graph.arrow
|
||||
"Bulk Ladybug ingest through in-memory Arrow.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.graph.debug
|
||||
"In-memory Ladybug sessions for the debug graph console."
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.graph.ingest
|
||||
"Penpot file -> Ladybug graph projection."
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.graph.ladybug
|
||||
"Ladybug access layer for graph-backed Penpot.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.graph.meta
|
||||
"`GraphMeta`: the graph's own account of who built it and from what.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.graph.projection.document
|
||||
"Project a Penpot file-data map into Ladybug nodes and structural edges.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.graph.projection.transforms
|
||||
"Derived graph links: edges a reader could compute from the projected
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.graph.report
|
||||
(:require
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.graph.schema
|
||||
"Ladybug DDL facade for the graph-backed Penpot vertical slice.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.graph.schema.contract
|
||||
"Deliberate choices in Penpot's graph schema, recorded as data.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.graph.schema.nodes
|
||||
"Single source of truth for graph node tables.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.graph.schema.projection
|
||||
"Derive Ladybug node column schemas from Penpot Malli sources.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.graph.schema.types
|
||||
"Map Malli schemas to Ladybug column types.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.graph.schema.values
|
||||
"Shape a Penpot value into the plain data its Ladybug column type wants.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.graph.stats
|
||||
(:require
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.graph.sync
|
||||
"Incremental Ladybug graph updates from Penpot file-change events."
|
||||
|
||||
@@ -180,7 +180,7 @@
|
||||
:file-id file-id
|
||||
:session-id session-id
|
||||
:profile-id profile-id}]
|
||||
(mbus/pub! msgbus
|
||||
(mbus/pub! msgbus
|
||||
:topic file-id
|
||||
:message message)))
|
||||
(recur)))
|
||||
@@ -226,13 +226,12 @@
|
||||
|
||||
(defmethod handle-message :pointer-update
|
||||
[{:keys [::mbus/msgbus]} {:keys [::ws/state ::session-id ::profile-id]} {:keys [file-id] :as message}]
|
||||
(when-let [subs (::file-subscription @state)]
|
||||
(when (= file-id (:file-id subs))
|
||||
(let [message (-> message
|
||||
(assoc :subs-id file-id)
|
||||
(assoc :profile-id profile-id)
|
||||
(assoc :session-id session-id))]
|
||||
(mbus/pub! msgbus :topic file-id :message message)))))
|
||||
(when (::file-subscription @state)
|
||||
(let [message (-> message
|
||||
(assoc :subs-id file-id)
|
||||
(assoc :profile-id profile-id)
|
||||
(assoc :session-id session-id))]
|
||||
(mbus/pub! msgbus :topic file-id :message message))))
|
||||
|
||||
(defmethod handle-message :default
|
||||
[_ {:keys [::ws/id]} message]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.rpc.commands.plugins
|
||||
(:require
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.storage.pending-gc
|
||||
"A maintenance task that reclaims storage objects created in 'pending'
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.tasks.demo-purge
|
||||
"Task handler for delayed demo profile deletion. Submitted at demo
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns backend-tests.demo-test
|
||||
(:require
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns backend-tests.graph-binder-gate-test
|
||||
"Binder gate for the incremental-sync statement templates.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns backend-tests.graph-sync-parity-test
|
||||
"Cold projection and incremental sync are two implementations of one mapping,
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
;; 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 Sucursal en España SL
|
||||
|
||||
(ns backend-tests.http-websocket-test
|
||||
(:require
|
||||
[app.common.uuid :as uuid]
|
||||
[app.db :as db]
|
||||
[app.http.websocket :as ws]
|
||||
[app.msgbus :as mbus]
|
||||
[app.rpc :as-alias rpc]
|
||||
[app.rpc.commands.files :as files]
|
||||
[app.rpc.commands.teams :as teams]
|
||||
[app.util.websocket :as util-ws]
|
||||
[backend-tests.helpers :as th]
|
||||
[clojure.test :as t]
|
||||
[promesa.exec.csp :as sp]))
|
||||
|
||||
(t/use-fixtures :once th/state-init)
|
||||
(t/use-fixtures :each th/database-reset)
|
||||
|
||||
(defn make-wsp
|
||||
[profile-id state output-ch]
|
||||
{::util-ws/id (uuid/next)
|
||||
::util-ws/state state
|
||||
::util-ws/output-ch output-ch
|
||||
::ws/profile-id profile-id
|
||||
::ws/session-id (uuid/next)})
|
||||
|
||||
(t/deftest subscribe-file-permission-check
|
||||
(let [profile1 (th/create-profile* 1 {:is-active true})
|
||||
profile2 (th/create-profile* 2 {:is-active true})
|
||||
file (th/create-file* 1 {:profile-id (:id profile1)
|
||||
:project-id (:default-project-id profile1)})
|
||||
cfg th/*system*
|
||||
state (atom {})
|
||||
output-ch (sp/chan :buf (sp/dropping-buffer 64))]
|
||||
|
||||
(t/testing "rejects unauthorized user"
|
||||
(let [wsp (make-wsp (:id profile2) state output-ch)]
|
||||
(t/is (thrown-with-msg?
|
||||
clojure.lang.ExceptionInfo
|
||||
#"not found"
|
||||
((get-method ws/handle-message :subscribe-file)
|
||||
cfg wsp {:file-id (:id file)})))))
|
||||
|
||||
(t/testing "permission check passes for authorized user"
|
||||
(t/is (nil? (files/check-read-permissions! cfg (:id profile1) (:id file)))))))
|
||||
|
||||
(t/deftest subscribe-team-permission-check
|
||||
(let [profile1 (th/create-profile* 1 {:is-active true})
|
||||
profile2 (th/create-profile* 2 {:is-active true})
|
||||
team (th/create-team* 1 {:profile-id (:id profile1)})
|
||||
cfg th/*system*
|
||||
state (atom {})
|
||||
output-ch (sp/chan :buf (sp/dropping-buffer 64))]
|
||||
|
||||
(t/testing "rejects unauthorized user"
|
||||
(let [wsp (make-wsp (:id profile2) state output-ch)]
|
||||
(t/is (thrown-with-msg?
|
||||
clojure.lang.ExceptionInfo
|
||||
#"not found"
|
||||
((get-method ws/handle-message :subscribe-team)
|
||||
cfg wsp {:team-id (:id team)})))))
|
||||
|
||||
(t/testing "permission check passes for authorized user"
|
||||
(t/is (nil? (teams/check-read-permissions! cfg (:id profile1) (:id team)))))))
|
||||
|
||||
(t/deftest pointer-update-validates-file-id
|
||||
(let [profile (th/create-profile* 1 {:is-active true})
|
||||
file (th/create-file* 1 {:profile-id (:id profile)
|
||||
:project-id (:default-project-id profile)})
|
||||
cfg th/*system*
|
||||
file-id (:id file)
|
||||
sub-ch (sp/chan :buf (sp/dropping-buffer 64))
|
||||
state (atom {::ws/file-subscription {:file-id file-id
|
||||
:channel sub-ch
|
||||
:topic file-id}})
|
||||
output-ch (sp/chan :buf (sp/dropping-buffer 64))
|
||||
wsp (make-wsp (:id profile) state output-ch)]
|
||||
|
||||
(t/testing "skips publish when file-id does not match subscription"
|
||||
(let [wrong-msg {:type :pointer-update
|
||||
:file-id (uuid/next)
|
||||
:position {:x 10 :y 20}
|
||||
:zoom 1.0}]
|
||||
(t/is (nil?
|
||||
((get-method ws/handle-message :pointer-update)
|
||||
cfg wsp wrong-msg)))))
|
||||
|
||||
(t/testing "does nothing when no file subscription exists"
|
||||
(let [empty-state (atom {})
|
||||
empty-wsp (make-wsp (:id profile) empty-state output-ch)
|
||||
msg {:type :pointer-update
|
||||
:file-id file-id
|
||||
:position {:x 10 :y 20}
|
||||
:zoom 1.0}]
|
||||
(t/is (nil?
|
||||
((get-method ws/handle-message :pointer-update)
|
||||
cfg empty-wsp msg)))))))
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns backend-tests.passwords-test
|
||||
(:require
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns backend-tests.rpc-demo-test
|
||||
(:require
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns backend-tests.rpc-plugins-test
|
||||
(:require
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
(ns app.common.files.changes
|
||||
(:require
|
||||
#?(:cljs [app.common.files.validate :as val])
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
@@ -428,7 +429,14 @@
|
||||
[:set-base-font-size
|
||||
[:map {:title "ModBaseFontSize"}
|
||||
[:type [:= :set-base-font-size]]
|
||||
[:base-font-size :string]]]])
|
||||
[:base-font-size :string]]]
|
||||
|
||||
[:validate-shapes
|
||||
[:map {:title "ValidateShapesChange"}
|
||||
[:type [:= :validate-shapes]]
|
||||
[:page-id ::sm/uuid]
|
||||
[:shape-ids [:vector ::sm/uuid]]
|
||||
[:context :string]]]])
|
||||
|
||||
(def schema:changes
|
||||
[:sequential {:gen/max 5 :gen/min 1} schema:change])
|
||||
@@ -464,7 +472,7 @@
|
||||
to the processor backend."
|
||||
nil)
|
||||
|
||||
(defmulti process-change (fn [_ change] (:type change)))
|
||||
(defmulti process-change (fn [_ change _] (:type change)))
|
||||
(defmulti process-operation (fn [_ op] (:type op)))
|
||||
|
||||
;; Changes Processing Impl
|
||||
@@ -496,22 +504,25 @@
|
||||
|
||||
(defn process-changes
|
||||
([data items]
|
||||
(process-changes data items true))
|
||||
(process-changes data items true {}))
|
||||
|
||||
([data items verify?]
|
||||
(process-changes data items verify? {}))
|
||||
|
||||
([data items verify? libraries]
|
||||
;; When verify? false we spec the schema validation. Currently used
|
||||
;; to make just 1 validation even if the changes are applied twice
|
||||
(when verify?
|
||||
(check-changes items))
|
||||
|
||||
(binding [*touched-changes* (volatile! #{})]
|
||||
(let [result (reduce #(or (process-change %1 %2) %1) data items)]
|
||||
(let [result (reduce #(or (process-change %1 %2 libraries) %1) data items)]
|
||||
(reduce process-touched-change result @*touched-changes*)))))
|
||||
|
||||
;; --- Comment Threads
|
||||
|
||||
(defmethod process-change :set-comment-thread-position
|
||||
[data {:keys [page-id comment-thread-id position frame-id]}]
|
||||
[data {:keys [page-id comment-thread-id position frame-id]} _]
|
||||
(d/update-in-when data [:pages-index page-id]
|
||||
(fn [page]
|
||||
(if (and position frame-id)
|
||||
@@ -524,7 +535,7 @@
|
||||
;; --- Guides
|
||||
|
||||
(defmethod process-change :set-guide
|
||||
[data {:keys [page-id id params]}]
|
||||
[data {:keys [page-id id params]} _]
|
||||
(if (nil? params)
|
||||
(d/update-in-when data [:pages-index page-id]
|
||||
(fn [page]
|
||||
@@ -540,7 +551,7 @@
|
||||
;; --- Flows
|
||||
|
||||
(defmethod process-change :set-flow
|
||||
[data {:keys [page-id id params]}]
|
||||
[data {:keys [page-id id params]} _]
|
||||
(if (nil? params)
|
||||
(d/update-in-when data [:pages-index page-id]
|
||||
(fn [page]
|
||||
@@ -556,7 +567,7 @@
|
||||
;; --- Grids
|
||||
|
||||
(defmethod process-change :set-default-grid
|
||||
[data {:keys [page-id grid-type params]}]
|
||||
[data {:keys [page-id grid-type params]} _]
|
||||
(if (nil? params)
|
||||
(d/update-in-when data [:pages-index page-id]
|
||||
(fn [page]
|
||||
@@ -593,7 +604,7 @@
|
||||
(update state :media-refs into xform media-refs)))
|
||||
|
||||
(defmethod process-change :add-obj
|
||||
[data {:keys [id obj page-id component-id frame-id parent-id index ignore-touched]}]
|
||||
[data {:keys [id obj page-id component-id frame-id parent-id index ignore-touched]} _]
|
||||
;; NOTE: we only perform hard validation on backend
|
||||
#?(:clj (validate-shape obj page-id))
|
||||
|
||||
@@ -628,7 +639,7 @@
|
||||
objects))
|
||||
|
||||
(defmethod process-change :mod-obj
|
||||
[data {:keys [page-id component-id] :as change}]
|
||||
[data {:keys [page-id component-id] :as change} _]
|
||||
(if page-id
|
||||
(d/update-in-when data [:pages-index page-id :objects] process-operations change)
|
||||
(d/update-in-when data [:components component-id :objects] process-operations change)))
|
||||
@@ -658,19 +669,19 @@
|
||||
objects))
|
||||
|
||||
(defmethod process-change :reorder-children
|
||||
[data {:keys [page-id component-id] :as change}]
|
||||
[data {:keys [page-id component-id] :as change} _]
|
||||
(if page-id
|
||||
(d/update-in-when data [:pages-index page-id :objects] process-children-reordering change)
|
||||
(d/update-in-when data [:components component-id :objects] process-children-reordering change)))
|
||||
|
||||
(defmethod process-change :del-obj
|
||||
[data {:keys [page-id component-id id ignore-touched]}]
|
||||
[data {:keys [page-id component-id id ignore-touched]} _]
|
||||
(if page-id
|
||||
(d/update-in-when data [:pages-index page-id] ctst/delete-shape id ignore-touched)
|
||||
(d/update-in-when data [:components component-id] ctst/delete-shape id ignore-touched)))
|
||||
|
||||
(defmethod process-change :fix-obj
|
||||
[data {:keys [page-id component-id id] :as params}]
|
||||
[data {:keys [page-id component-id id] :as params} _]
|
||||
(letfn [(fix-container [container]
|
||||
(case (:fix params :broken-children)
|
||||
:broken-children (ctst/fix-broken-children container id)
|
||||
@@ -682,7 +693,7 @@
|
||||
(d/update-in-when data [:components component-id] fix-container))))
|
||||
|
||||
(defmethod process-change :reg-objects
|
||||
[data {:keys [page-id component-id shapes]}]
|
||||
[data {:keys [page-id component-id shapes]} _]
|
||||
;; FIXME: Improve performance
|
||||
(letfn [(reg-objects [objects]
|
||||
(let [lookup (d/getf objects)
|
||||
@@ -734,7 +745,7 @@
|
||||
|
||||
(defmethod process-change :mov-objects
|
||||
;; FIXME: ignore-touched is no longer used, so we can consider it deprecated
|
||||
[data {:keys [parent-id shapes index page-id component-id #_ignore-touched after-shape allow-altering-copies syncing]}]
|
||||
[data {:keys [parent-id shapes index page-id component-id #_ignore-touched after-shape allow-altering-copies syncing]} _]
|
||||
(letfn [(calculate-invalid-targets [objects shape-id]
|
||||
(let [reduce-fn #(into %1 (calculate-invalid-targets objects %2))]
|
||||
(->> (get-in objects [shape-id :shapes])
|
||||
@@ -849,7 +860,7 @@
|
||||
(d/update-in-when data [:components component-id :objects] move-objects))))
|
||||
|
||||
(defmethod process-change :add-page
|
||||
[data {:keys [id name page]}]
|
||||
[data {:keys [id name page]} _]
|
||||
(when (and id name page)
|
||||
(ex/raise :type :conflict
|
||||
:hint "id+name or page should be provided, never both"))
|
||||
@@ -859,7 +870,7 @@
|
||||
(ctpl/add-page data page)))
|
||||
|
||||
(defmethod process-change :mod-page
|
||||
[data {:keys [id] :as params}]
|
||||
[data {:keys [id] :as params} _]
|
||||
(d/update-in-when data [:pages-index id]
|
||||
(fn [page]
|
||||
(let [name (get params :name)
|
||||
@@ -889,7 +900,7 @@
|
||||
(dissoc :pixel-grid-opacity))))))
|
||||
|
||||
(defmethod process-change :set-plugin-data
|
||||
[data {:keys [object-type object-id page-id namespace key value]}]
|
||||
[data {:keys [object-type object-id page-id namespace key value]} _]
|
||||
(letfn [(update-fn [data]
|
||||
(if (some? value)
|
||||
(assoc-in data [:plugin-data namespace key] value)
|
||||
@@ -915,83 +926,83 @@
|
||||
(d/update-in-when data [:components object-id] update-fn))))
|
||||
|
||||
(defmethod process-change :del-page
|
||||
[data {:keys [id]}]
|
||||
[data {:keys [id]} _]
|
||||
(ctpl/delete-page data id))
|
||||
|
||||
(defmethod process-change :mov-page
|
||||
[data {:keys [id index]}]
|
||||
[data {:keys [id index]} _]
|
||||
(update data :pages d/insert-at-index index [id]))
|
||||
|
||||
(defmethod process-change :add-color
|
||||
[data {:keys [color]}]
|
||||
[data {:keys [color]} _]
|
||||
(ctl/add-color data color))
|
||||
|
||||
(defmethod process-change :mod-color
|
||||
[data {:keys [color]}]
|
||||
[data {:keys [color]} _]
|
||||
(ctl/set-color data color))
|
||||
|
||||
(defmethod process-change :del-color
|
||||
[data {:keys [id]}]
|
||||
[data {:keys [id]} _]
|
||||
(ctl/delete-color data id))
|
||||
|
||||
;; -- Media
|
||||
|
||||
(defmethod process-change :add-media
|
||||
[data {:keys [object]}]
|
||||
[data {:keys [object]} _]
|
||||
(update data :media assoc (:id object) object))
|
||||
|
||||
(defmethod process-change :mod-media
|
||||
[data {:keys [object]}]
|
||||
[data {:keys [object]} _]
|
||||
(d/update-in-when data [:media (:id object)] merge object))
|
||||
|
||||
(defmethod process-change :del-media
|
||||
[data {:keys [id]}]
|
||||
[data {:keys [id]} _]
|
||||
(d/update-when data :media dissoc id))
|
||||
|
||||
;; -- Components
|
||||
|
||||
(defmethod process-change :add-component
|
||||
[data params]
|
||||
[data params _]
|
||||
(ctkl/add-component data params))
|
||||
|
||||
(defmethod process-change :mod-component
|
||||
[data params]
|
||||
[data params _]
|
||||
(ctkl/mod-component data params))
|
||||
|
||||
(defmethod process-change :del-component
|
||||
[data {:keys [id skip-undelete? delta]}]
|
||||
[data {:keys [id skip-undelete? delta]} _]
|
||||
(ctf/delete-component data id skip-undelete? delta))
|
||||
|
||||
(defmethod process-change :restore-component
|
||||
[data {:keys [id page-id]}]
|
||||
[data {:keys [id page-id]} _]
|
||||
(ctf/restore-component data id page-id))
|
||||
|
||||
(defmethod process-change :purge-component
|
||||
[data {:keys [id]}]
|
||||
[data {:keys [id]} _]
|
||||
(ctf/purge-component data id))
|
||||
|
||||
;; -- Typography
|
||||
|
||||
(defmethod process-change :add-typography
|
||||
[data {:keys [typography]}]
|
||||
[data {:keys [typography]} _]
|
||||
(ctyl/add-typography data typography))
|
||||
|
||||
(defmethod process-change :mod-typography
|
||||
[data {:keys [typography]}]
|
||||
[data {:keys [typography]} _]
|
||||
(ctyl/update-typography data (:id typography) merge typography))
|
||||
|
||||
(defmethod process-change :del-typography
|
||||
[data {:keys [id]}]
|
||||
[data {:keys [id]} _]
|
||||
(ctyl/delete-typography data id))
|
||||
|
||||
;; -- Design Tokens
|
||||
|
||||
(defmethod process-change :set-tokens-lib
|
||||
[data {:keys [tokens-lib]}]
|
||||
[data {:keys [tokens-lib]} _]
|
||||
(assoc data :tokens-lib tokens-lib))
|
||||
|
||||
(defmethod process-change :set-token
|
||||
[data {:keys [set-id token-id attrs]}]
|
||||
[data {:keys [set-id token-id attrs]} _]
|
||||
(update data :tokens-lib
|
||||
(fn [lib]
|
||||
(let [lib' (ctob/ensure-tokens-lib lib)]
|
||||
@@ -1008,7 +1019,7 @@
|
||||
(ctob/make-token (merge prev-token attrs)))))))))
|
||||
|
||||
(defmethod process-change :set-token-set
|
||||
[data {:keys [id attrs]}]
|
||||
[data {:keys [id attrs]} _]
|
||||
(update data :tokens-lib
|
||||
(fn [lib]
|
||||
(let [lib' (ctob/ensure-tokens-lib lib)]
|
||||
@@ -1023,7 +1034,7 @@
|
||||
(ctob/update-set lib' id (fn [_] (ctob/make-token-set attrs))))))))
|
||||
|
||||
(defmethod process-change :set-token-theme
|
||||
[data {:keys [id attrs]}]
|
||||
[data {:keys [id attrs]} _]
|
||||
(update data :tokens-lib
|
||||
(fn [lib]
|
||||
(let [lib' (ctob/ensure-tokens-lib lib)]
|
||||
@@ -1041,35 +1052,67 @@
|
||||
(ctob/make-token-theme (merge prev-token-theme attrs)))))))))
|
||||
|
||||
(defmethod process-change :set-active-token-themes
|
||||
[data {:keys [theme-paths]}]
|
||||
[data {:keys [theme-paths]} _]
|
||||
(update data :tokens-lib #(-> % (ctob/ensure-tokens-lib)
|
||||
(ctob/set-active-themes theme-paths))))
|
||||
|
||||
(defmethod process-change :rename-token-set-group
|
||||
[data {:keys [set-group-path set-group-fname]}]
|
||||
[data {:keys [set-group-path set-group-fname]} _]
|
||||
(update data :tokens-lib (fn [lib]
|
||||
(-> lib
|
||||
(ctob/ensure-tokens-lib)
|
||||
(ctob/rename-set-group set-group-path set-group-fname)))))
|
||||
|
||||
(defmethod process-change :move-token-set
|
||||
[data {:keys [from-path to-path before-path before-group] :as changes}]
|
||||
[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))))
|
||||
|
||||
(defmethod process-change :move-token-set-group
|
||||
[data {:keys [from-path to-path before-path before-group]}]
|
||||
[data {:keys [from-path to-path before-path before-group]} _]
|
||||
(update data :tokens-lib #(-> %
|
||||
(ctob/ensure-tokens-lib)
|
||||
(ctob/move-set-group from-path to-path before-path before-group))))
|
||||
|
||||
;; === Design Tokens configuration
|
||||
;; --- Design Tokens configuration
|
||||
|
||||
(defmethod process-change :set-base-font-size
|
||||
[data {:keys [base-font-size]}]
|
||||
[data {:keys [base-font-size]} _]
|
||||
(ctf/set-base-font-size data base-font-size))
|
||||
|
||||
;; --- Validate Shapes
|
||||
|
||||
#?(:clj
|
||||
(defmethod process-change :validate-shapes
|
||||
[data _ _]
|
||||
data))
|
||||
|
||||
#?(:cljs
|
||||
(defmethod process-change :validate-shapes
|
||||
[data {:keys [page-id shape-ids context]} libraries]
|
||||
(if libraries
|
||||
(println "Validating shapes: \n"
|
||||
" page-id:" (str page-id) "\n"
|
||||
" shape-ids:" (str shape-ids) "\n"
|
||||
" context:" context)
|
||||
(let [file {:data data :id uuid/zero}
|
||||
errors (reduce (fn [acc shape-id]
|
||||
(if-let [page (ctpl/get-page data page-id)]
|
||||
(let [page-errors (val/validate-shape shape-id file page libraries)]
|
||||
(if (seq page-errors)
|
||||
(into acc page-errors)
|
||||
acc))
|
||||
acc))
|
||||
[]
|
||||
shape-ids)]
|
||||
(when (seq errors)
|
||||
(ex/raise :type :validation
|
||||
:code :referential-integrity
|
||||
:hint (str "error on validating shapes: " context)
|
||||
:details errors))
|
||||
data))
|
||||
data))
|
||||
|
||||
;; === Operations
|
||||
|
||||
|
||||
@@ -1203,7 +1203,6 @@
|
||||
[changes]
|
||||
(::page-id (meta changes)))
|
||||
|
||||
|
||||
(defn set-text-content
|
||||
[changes id content prev-content]
|
||||
(assert-page-id! changes)
|
||||
@@ -1224,3 +1223,12 @@
|
||||
(-> changes
|
||||
(update :redo-changes conj redo-change)
|
||||
(update :undo-changes conj undo-change))))
|
||||
|
||||
;; Validate Shapes
|
||||
|
||||
(defn validate-shapes
|
||||
[changes page-id shape-ids context]
|
||||
(update changes :redo-changes conj {:type :validate-shapes
|
||||
:page-id page-id
|
||||
:shape-ids (vec shape-ids)
|
||||
:context context}))
|
||||
@@ -10,7 +10,6 @@
|
||||
[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.path-names :as cpn]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.component :as ctk]
|
||||
@@ -569,7 +568,17 @@
|
||||
objects (:objects page)
|
||||
file-data (:data file)
|
||||
first-child (get objects (first shapes))
|
||||
prop-names (cfv/extract-properties-names first-child file-data)]
|
||||
|
||||
extract-properties-names
|
||||
(fn [shape]
|
||||
;; Get the names of the properties of the shape's component
|
||||
(->> shape
|
||||
(#(ctkl/get-component file-data (:component-id %) true))
|
||||
:variant-properties
|
||||
(map :name)))
|
||||
|
||||
prop-names (extract-properties-names first-child)]
|
||||
|
||||
(run! (fn [child-id]
|
||||
(when-let [child (get objects child-id)]
|
||||
(if (not (ctk/is-variant? child))
|
||||
@@ -583,7 +592,7 @@
|
||||
(str/ffmt "Main instance in variant % should have the variant-id of the container but has %" (:id child) (:variant-id child))
|
||||
child file page
|
||||
:variant-id shape-id))
|
||||
(when (not= prop-names (cfv/extract-properties-names child file-data))
|
||||
(when (not= prop-names (extract-properties-names child))
|
||||
(report-error :invalid-variant-properties
|
||||
(str/ffmt "Variant % has invalid properties %" (:id child) (vec prop-names))
|
||||
child file page
|
||||
|
||||
@@ -6,12 +6,15 @@
|
||||
(ns app.common.files.variant
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.component :as ctc]
|
||||
[app.common.types.components-list :as ctcl]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.variant :as ctv]))
|
||||
|
||||
(defn find-variant-components
|
||||
"Find a list of the components that belongs to this variant-id"
|
||||
"Find the components that belong to the variant container identified by `variant-id`,
|
||||
preserving the order defined by the container's shapes.
|
||||
|
||||
Example return:
|
||||
(<component1> <component2> ...)"
|
||||
([data variant-id]
|
||||
(let [page-id (->> data
|
||||
:components
|
||||
@@ -22,22 +25,24 @@
|
||||
objects (dm/get-in data [:pages-index page-id :objects])]
|
||||
(find-variant-components data objects variant-id)))
|
||||
([data objects variant-id]
|
||||
(assert (or (uuid? variant-id) (nil? variant-id)))
|
||||
;; We can't simply filter components, because we need to maintain the order
|
||||
(->> (dm/get-in objects [variant-id :shapes])
|
||||
(map #(dm/get-in objects [% :component-id]))
|
||||
(map #(ctcl/get-component data % true))
|
||||
reverse)))
|
||||
|
||||
(defn extract-properties-names
|
||||
[shape data]
|
||||
(->> shape
|
||||
(#(ctcl/get-component data (:component-id %) true))
|
||||
:variant-properties
|
||||
(map :name)))
|
||||
(let [container (get objects variant-id)]
|
||||
(if (ctv/variant-container? container)
|
||||
(->> (:shapes container)
|
||||
(map #(dm/get-in objects [% :component-id]))
|
||||
(map #(ctkl/get-component data % true))
|
||||
reverse)
|
||||
[]))))
|
||||
|
||||
(defn extract-properties-values
|
||||
"Get a map of properties associated to their possible values"
|
||||
"Get a map of variant property names to their distinct possible values,
|
||||
collected from all components that belong to the variant container.
|
||||
|
||||
Example return:
|
||||
[{:name 'Property 1' :value ('Value1' 'Value2')}]"
|
||||
[data objects variant-id]
|
||||
(assert (or (uuid? variant-id) (nil? variant-id)))
|
||||
(->> (find-variant-components data objects variant-id)
|
||||
(mapcat :variant-properties)
|
||||
(group-by :name)
|
||||
@@ -47,9 +52,13 @@
|
||||
:value (->> v (map :value) distinct)}
|
||||
mdata))))))
|
||||
|
||||
(defn get-variant-mains
|
||||
[component data]
|
||||
(assert (ctv/valid-variant-component? component) "expected valid component variant")
|
||||
(defn- get-variant-mains
|
||||
"Return the ids of the main instance shapes of the variant this component belongs to,
|
||||
in the order they appear in the container.
|
||||
|
||||
Example return:
|
||||
[<main-shape-a-id> <main-shape-b-id>]"
|
||||
[data component]
|
||||
(when-let [variant-id (:variant-id component)]
|
||||
(let [page-id (:main-instance-page component)
|
||||
objects (-> (dm/get-in data [:pages-index page-id])
|
||||
@@ -57,27 +66,20 @@
|
||||
(dm/get-in objects [variant-id :shapes]))))
|
||||
|
||||
(defn is-secondary-variant?
|
||||
[component data]
|
||||
(let [shapes (get-variant-mains component data)]
|
||||
"Return true if the component is a secondary variant in its variant container.
|
||||
The primary variant is the last one in the container's children list.
|
||||
Return false if the component is the primary variant or if it's not part of a variant."
|
||||
[data component]
|
||||
(let [shapes (get-variant-mains data component)]
|
||||
(and (seq shapes)
|
||||
(not= (:main-instance-id component) (last shapes)))))
|
||||
|
||||
(defn get-primary-variant
|
||||
"Return the main instance of the primary variant (the last one) in the variant container."
|
||||
[data component]
|
||||
(let [page-id (:main-instance-page component)
|
||||
objects (-> (dm/get-in data [:pages-index page-id])
|
||||
(get :objects))
|
||||
variant-id (:variant-id component)]
|
||||
(->> (dm/get-in objects [variant-id :shapes])
|
||||
(let [page-id (:main-instance-page component)
|
||||
objects (-> (dm/get-in data [:pages-index page-id])
|
||||
(get :objects))]
|
||||
(->> (get-variant-mains data component)
|
||||
peek
|
||||
(get objects))))
|
||||
|
||||
(defn get-primary-component
|
||||
[data component-id]
|
||||
(when-let [component (ctcl/get-component data component-id)]
|
||||
(if (ctc/is-variant? component)
|
||||
(->> component
|
||||
(get-primary-variant data)
|
||||
:component-id
|
||||
(ctcl/get-component data))
|
||||
component)))
|
||||
@@ -290,8 +290,11 @@
|
||||
duplicated-parent?
|
||||
(->> ids-map vals (some #(= % (:parent-id first-shape))))
|
||||
|
||||
grid-parent?
|
||||
(and (ctsl/grid-layout? objects (:parent-id first-shape)) (not duplicated-parent?))
|
||||
|
||||
changes
|
||||
(if (and (ctsl/grid-layout? objects (:parent-id first-shape)) (not duplicated-parent?))
|
||||
(if grid-parent?
|
||||
(let [target-cell (-> position meta :cell)
|
||||
|
||||
[row column]
|
||||
@@ -313,7 +316,19 @@
|
||||
changes
|
||||
(reduce #(pcb/add-object %1 %2 {:ignore-touched true})
|
||||
changes
|
||||
(rest new-shapes))]
|
||||
(rest new-shapes))
|
||||
|
||||
ids-to-validate (cond-> [(:id first-shape)]
|
||||
grid-parent?
|
||||
(conj (:parent-id first-shape)))
|
||||
|
||||
changes (if (seq ids-to-validate)
|
||||
(pcb/validate-shapes changes
|
||||
(:id page)
|
||||
ids-to-validate
|
||||
(str "generate-instantiate-component: " component-id
|
||||
" under parent-id" (or parent-id " root")))
|
||||
changes)]
|
||||
|
||||
[new-shape changes])))
|
||||
|
||||
@@ -3123,7 +3138,6 @@
|
||||
;; we calculate a new one because the components will have created new shapes.
|
||||
ids-map (into {} (map #(vector % (uuid/next))) all-ids)
|
||||
|
||||
|
||||
;; If there is an alt-duplication we change to root
|
||||
;; For variants so the copy is made as a child of root
|
||||
;; This is because inside a variant-container can't be a copy
|
||||
@@ -3135,7 +3149,6 @@
|
||||
(assoc :parent-id uuid/zero :frame-id uuid/zero)))
|
||||
shapes)
|
||||
|
||||
|
||||
changes (-> changes
|
||||
(pcb/with-page page)
|
||||
(pcb/with-objects all-objects)
|
||||
@@ -3165,7 +3178,20 @@
|
||||
(comp
|
||||
(filter #(= :add-obj (:type %)))
|
||||
(map #(vector (:old-id %) (-> % :obj :id))))
|
||||
(:redo-changes changes))]
|
||||
(:redo-changes changes))
|
||||
|
||||
copied-components
|
||||
(ctn/get-all-instance-roots (:objects page) ids)
|
||||
|
||||
ids-to-validate
|
||||
(map #(get ids-map % %) copied-components)
|
||||
|
||||
changes (if (seq ids-to-validate)
|
||||
(pcb/validate-shapes changes
|
||||
(:id page)
|
||||
ids-to-validate
|
||||
(cond-> (str "generate-duplicate-changes: " ids)))
|
||||
changes)]
|
||||
|
||||
(-> changes
|
||||
(generate-duplicate-flows shapes page ids-map)
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
(reduce check-shape changes mod-obj-changes)))
|
||||
|
||||
(defn generate-update-shapes
|
||||
[changes ids update-fn objects {:keys [attrs changed-sub-attr ignore-tree ignore-touched with-objects? translation?]}]
|
||||
[changes ids update-fn objects {:keys [attrs changed-sub-attr ignore-tree ignore-touched with-objects? translation? extra-context]}]
|
||||
(let [changes (reduce
|
||||
(fn [changes id]
|
||||
(let [opts {:attrs attrs
|
||||
@@ -96,7 +96,19 @@
|
||||
(pcb/reorder-grid-children ids))
|
||||
|
||||
(not ignore-touched)
|
||||
(generate-unapply-tokens objects changed-sub-attr))]
|
||||
(generate-unapply-tokens objects changed-sub-attr))
|
||||
|
||||
page-id (pcb/get-page-id changes)
|
||||
modified-components (ctn/get-all-instance-roots objects ids)
|
||||
|
||||
changes (if (and page-id (seq modified-components))
|
||||
(pcb/validate-shapes changes
|
||||
page-id
|
||||
modified-components
|
||||
(cond-> (str "generate-update-shapes: " ids " " attrs)
|
||||
(some? extra-context)
|
||||
(str " \n -> from " extra-context)))
|
||||
changes)]
|
||||
changes))
|
||||
|
||||
(defn- generate-update-shape-flags
|
||||
@@ -248,8 +260,8 @@
|
||||
page-id (pcb/get-page-id changes)
|
||||
page (or (pcb/get-page changes)
|
||||
(ctpl/get-page data page-id))
|
||||
|
||||
ids (cfh/clean-loops objects ids)
|
||||
|
||||
in-component-copy?
|
||||
(fn [shape-id]
|
||||
;; Look for shapes that are inside a component copy, but are
|
||||
@@ -258,7 +270,7 @@
|
||||
;; If we want to specifically allow altering the copies, this is
|
||||
;; a special case, like a component swap, in which case we want
|
||||
;; to delete the old shape
|
||||
(let [shape (get objects shape-id)]
|
||||
(let [shape (get objects shape-id)]
|
||||
(and (ctn/has-any-copy-parent? objects shape)
|
||||
(not allow-altering-copies))))
|
||||
|
||||
@@ -437,7 +449,19 @@
|
||||
(into []
|
||||
(remove #(and (ctsi/has-destination %)
|
||||
(id-to-delete? (:destination %))))
|
||||
interactions))))))]
|
||||
interactions))))))
|
||||
|
||||
modified-components (ctn/get-all-instance-roots objects (disj all-parents uuid/zero))
|
||||
;; There is no need to validate deleted objects. Probably also no need to validate hidden or unmasked objects,
|
||||
;; but we may think of it
|
||||
|
||||
changes (if (seq modified-components)
|
||||
(pcb/validate-shapes changes
|
||||
page-id
|
||||
modified-components
|
||||
(str "generate-delete-shapes: " ids))
|
||||
changes)]
|
||||
|
||||
[all-parents changes])))
|
||||
|
||||
|
||||
|
||||
@@ -172,10 +172,10 @@
|
||||
new-props (- min-props
|
||||
(+ (count props)
|
||||
(if add-name? 1 0)))
|
||||
props (ctv/add-new-props props (repeat new-props ""))]
|
||||
props (ctv/add-new-properties props (repeat new-props ""))]
|
||||
|
||||
(if add-name?
|
||||
(ctv/add-new-prop props (:name component))
|
||||
(ctv/add-new-property props (:name component))
|
||||
props)))
|
||||
|
||||
(defn- create-new-properties-from-non-variant
|
||||
|
||||
@@ -72,13 +72,6 @@
|
||||
[:map {:title "PlainColorAttrs"}
|
||||
[:color schema:hex-color]])
|
||||
|
||||
(def schema:image-transform
|
||||
[:map {:title "ImageTransform" :closed true}
|
||||
[:x {:optional true} ::sm/safe-number]
|
||||
[:y {:optional true} ::sm/safe-number]
|
||||
[:width {:optional true} ::sm/safe-number]
|
||||
[:height {:optional true} ::sm/safe-number]])
|
||||
|
||||
(def schema:image
|
||||
[:map {:title "ImageColor" :closed true}
|
||||
[:width [::sm/int {:min 0 :gen/gen sg/int}]]
|
||||
@@ -86,8 +79,7 @@
|
||||
[:mtype {:gen/gen (sg/elements cm/image-types)} ::sm/text]
|
||||
[:id ::sm/uuid]
|
||||
[:name {:optional true} ::sm/text]
|
||||
[:keep-aspect-ratio {:optional true} :boolean]
|
||||
[:transform {:optional true} schema:image-transform]])
|
||||
[:keep-aspect-ratio {:optional true} :boolean]])
|
||||
|
||||
(def image-attrs
|
||||
"A set of attrs that corresponds to image data type"
|
||||
|
||||
@@ -216,6 +216,41 @@
|
||||
:else
|
||||
(get-instance-root objects (get objects (:parent-id shape)))))
|
||||
|
||||
(defn get-all-instance-roots
|
||||
"Given a list of shape ids and an objects tree, returns a set with the ids of
|
||||
all instance roots that are at, above or below any of the shapes identified by
|
||||
the given list. An instance root is a shape that has :component-root set to
|
||||
true (checked by ctk/instance-root?). There is at most one instance root in
|
||||
any subtree rooted at an instance root, so the downward search stops at the
|
||||
first instance root found in each branch. Uses a visited set to avoid
|
||||
reprocessing the same shapes."
|
||||
[objects shape-ids]
|
||||
(let [visited (atom #{})
|
||||
result (atom #{})]
|
||||
(letfn [(search-up [shape-id]
|
||||
(when-not (contains? @visited shape-id)
|
||||
(swap! visited conj shape-id)
|
||||
(let [shape (get objects shape-id)]
|
||||
(when-not (nil? shape)
|
||||
(if (ctk/instance-root? shape)
|
||||
(swap! result conj (:id shape))
|
||||
(when-not (cfh/root? shape)
|
||||
(when-let [parent-id (:parent-id shape)]
|
||||
(search-up parent-id))))))))
|
||||
(search-down [shape-id]
|
||||
(when-not (contains? @visited shape-id)
|
||||
(swap! visited conj shape-id)
|
||||
(let [shape (get objects shape-id)]
|
||||
(when-not (nil? shape)
|
||||
(if (ctk/instance-root? shape)
|
||||
(swap! result conj (:id shape))
|
||||
(doseq [child-id (:shapes shape)]
|
||||
(search-down child-id)))))))]
|
||||
(doseq [shape-id shape-ids]
|
||||
(search-up shape-id)
|
||||
(search-down shape-id))
|
||||
@result)))
|
||||
|
||||
(defn find-component-main
|
||||
"If the shape is a component main instance or is inside one, return that instance.
|
||||
Uses an iterative loop with cycle detection to prevent stack overflow on circular
|
||||
|
||||
@@ -119,15 +119,12 @@
|
||||
|
||||
(defn write-image-fill
|
||||
[offset buffer opacity image]
|
||||
(let [image-id (get image :id)
|
||||
image-width (get image :width)
|
||||
image-height (get image :height)
|
||||
alpha (mth/floor (* opacity 0xff))
|
||||
keep-aspect-ratio (if (get image :keep-aspect-ratio false) 0x01 0x00)
|
||||
transform (get image :transform)
|
||||
has-transform? (some? transform)
|
||||
transform-flag (if has-transform? 0x02 0x00)
|
||||
flags (bit-or keep-aspect-ratio transform-flag)]
|
||||
(let [image-id (get image :id)
|
||||
image-width (get image :width)
|
||||
image-height (get image :height)
|
||||
alpha (mth/floor (* opacity 0xff))
|
||||
keep-aspect-ratio (if (get image :keep-aspect-ratio false) 0x01 0x00)
|
||||
flags (bit-or keep-aspect-ratio 0x00)]
|
||||
(buf/write-byte buffer (+ offset 0) 0x03)
|
||||
(buf/write-uuid buffer (+ offset 4) image-id)
|
||||
(buf/write-byte buffer (+ offset 20) alpha)
|
||||
@@ -135,17 +132,6 @@
|
||||
(buf/write-short buffer (+ offset 22) 0) ;; 2-byte padding (reserved for future use)
|
||||
(buf/write-int buffer (+ offset 24) image-width)
|
||||
(buf/write-int buffer (+ offset 28) image-height)
|
||||
(if has-transform?
|
||||
(do
|
||||
(buf/write-float buffer (+ offset 32) (double (get transform :x 0.0)))
|
||||
(buf/write-float buffer (+ offset 36) (double (get transform :y 0.0)))
|
||||
(buf/write-float buffer (+ offset 40) (double (get transform :width 1.0)))
|
||||
(buf/write-float buffer (+ offset 44) (double (get transform :height 1.0))))
|
||||
(do
|
||||
(buf/write-float buffer (+ offset 32) 0.0)
|
||||
(buf/write-float buffer (+ offset 36) 0.0)
|
||||
(buf/write-float buffer (+ offset 40) 1.0)
|
||||
(buf/write-float buffer (+ offset 44) 1.0)))
|
||||
(+ offset FILL-U8-SIZE)))
|
||||
|
||||
(defn- write-metadata
|
||||
@@ -222,36 +208,28 @@
|
||||
:type type}})
|
||||
|
||||
3 ;; image fill
|
||||
(let [id (buf/read-uuid dbuffer (+ doffset 4))
|
||||
alpha (buf/read-unsigned-byte dbuffer (+ doffset 20))
|
||||
opacity (mth/precision (/ alpha 0xff) 2)
|
||||
flags (buf/read-unsigned-byte dbuffer (+ doffset 21))
|
||||
ratio (not (zero? (bit-and flags 0x01)))
|
||||
has-tf (not (zero? (bit-and flags 0x02)))
|
||||
width (buf/read-int dbuffer (+ doffset 24))
|
||||
height (buf/read-int dbuffer (+ doffset 28))
|
||||
transform (when has-tf
|
||||
{:x (buf/read-float dbuffer (+ doffset 32))
|
||||
:y (buf/read-float dbuffer (+ doffset 36))
|
||||
:width (buf/read-float dbuffer (+ doffset 40))
|
||||
:height (buf/read-float dbuffer (+ doffset 44))})
|
||||
mtype (buf/read-short mbuffer (+ moffset 2))
|
||||
mtype (case mtype
|
||||
0x01 "image/jpeg"
|
||||
0x02 "image/png"
|
||||
0x03 "image/gif"
|
||||
0x04 "image/webp"
|
||||
0x05 "image/svg+xml")]
|
||||
(let [id (buf/read-uuid dbuffer (+ doffset 4))
|
||||
alpha (buf/read-unsigned-byte dbuffer (+ doffset 20))
|
||||
opacity (mth/precision (/ alpha 0xff) 2)
|
||||
flags (buf/read-unsigned-byte dbuffer (+ doffset 21))
|
||||
ratio (boolean (bit-and flags 0x01))
|
||||
width (buf/read-int dbuffer (+ doffset 24))
|
||||
height (buf/read-int dbuffer (+ doffset 28))
|
||||
mtype (buf/read-short mbuffer (+ moffset 2))
|
||||
mtype (case mtype
|
||||
0x01 "image/jpeg"
|
||||
0x02 "image/png"
|
||||
0x03 "image/gif"
|
||||
0x04 "image/webp"
|
||||
0x05 "image/svg+xml")]
|
||||
{:fill-opacity opacity
|
||||
:fill-image (cond-> {:id id
|
||||
:width width
|
||||
:height height
|
||||
:mtype mtype
|
||||
:keep-aspect-ratio ratio
|
||||
;; FIXME: we are not encodign the name, looks useless
|
||||
:name "sample"}
|
||||
(some? transform)
|
||||
(assoc :transform transform))}))]
|
||||
:fill-image {:id id
|
||||
:width width
|
||||
:height height
|
||||
:mtype mtype
|
||||
:keep-aspect-ratio ratio
|
||||
;; FIXME: we are not encodign the name, looks useless
|
||||
:name "sample"}}))]
|
||||
|
||||
(if refs?
|
||||
(let [ref-file (buf/read-uuid mbuffer (+ moffset 4))
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.common.types.path.fit
|
||||
"Curve fitting helpers."
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.common.types.path.selection
|
||||
"Transforms selected path nodes and handlers."
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.common.types.tokens-status
|
||||
(:require
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
[:variant-id {:optional true} ::sm/uuid]
|
||||
[:variant-properties {:optional true} [:vector schema:variant-property]]])
|
||||
|
||||
(def valid-variant-component?
|
||||
(sm/check-fn schema:variant-component))
|
||||
|
||||
(def schema:variant-shape
|
||||
"The root shape of the main instance of a variant component"
|
||||
[:map
|
||||
@@ -34,14 +37,17 @@
|
||||
[:variant-name {:optional true} :string]
|
||||
[:variant-error {:optional true} :string]])
|
||||
|
||||
(def valid-variant-shape?
|
||||
(sm/check-fn schema:variant-shape))
|
||||
|
||||
(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]])
|
||||
|
||||
(def valid-variant-component?
|
||||
(sm/check-fn schema:variant-component))
|
||||
(def valid-variant-container?
|
||||
(sm/check-fn schema:variant-container))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@@ -50,17 +56,41 @@
|
||||
(def property-max-length 60)
|
||||
(def value-prefix "Value ")
|
||||
|
||||
(defn variant-component?
|
||||
[component]
|
||||
(some? (:variant-id component)))
|
||||
|
||||
(defn variant-shape?
|
||||
[shape]
|
||||
(some? (:variant-id shape)))
|
||||
|
||||
(defn variant-container?
|
||||
[shape]
|
||||
(some? (:is-variant-container shape)))
|
||||
|
||||
(defn properties-to-name
|
||||
"Transform the properties into a name, with the values separated by comma"
|
||||
"Transform the properties into a name, with the values separated by comma, excluding the empty ones.
|
||||
|
||||
Example:
|
||||
|
||||
[{:name 'Property 1' :value 'Button'}
|
||||
{:name 'Property 2' :value 'Primary'}] -> 'Button, Primary'"
|
||||
[properties]
|
||||
(assert (or (sequential? properties) (nil? 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"
|
||||
"Returns the next property number, to avoid duplicates on the property names.
|
||||
|
||||
Example:
|
||||
|
||||
[{:name 'Property 1' :value 'x'}
|
||||
{:name 'Property 3' :value 'y'}] -> 4"
|
||||
[properties]
|
||||
(assert (or (sequential? properties) (nil? properties)))
|
||||
(let [numbers (keep
|
||||
#(some->> (:name %) (re-find property-regex) second d/parse-integer)
|
||||
properties)
|
||||
@@ -69,38 +99,70 @@
|
||||
0)]
|
||||
(inc (max max-num (count properties)))))
|
||||
|
||||
(defn add-new-prop
|
||||
"Adds a new property with generated name and provided value to the existing props list."
|
||||
[props value]
|
||||
(conj props {:name (str property-prefix (next-property-number props))
|
||||
:value value}))
|
||||
(defn add-new-property
|
||||
"Adds a new property with generated name and provided value to the existing properties list.
|
||||
|
||||
(defn add-new-props
|
||||
"Adds new properties with generated names and provided values to the existing props list."
|
||||
[props values]
|
||||
(let [next-prop-num (next-property-number props)
|
||||
Example:
|
||||
|
||||
[{:name 'Property 1' :value 'x'}] 'y' -> [{:name 'Property 1' :value 'x'}
|
||||
{:name 'Property 2' :value 'y'}]"
|
||||
[properties value]
|
||||
(assert (or (sequential? properties) (nil? properties)))
|
||||
(assert (or (string? value) (nil? value)))
|
||||
(conj properties {:name (str property-prefix (next-property-number properties))
|
||||
:value value}))
|
||||
|
||||
(defn add-new-properties
|
||||
"Adds new properties with generated names and provided values to the existing properties list.
|
||||
|
||||
Example:
|
||||
|
||||
[{:name 'Property 1' :value 'x'}] ['a' 'b'] -> [{:name 'Property 1' :value 'x'}
|
||||
{:name 'Property 2' :value 'a'}
|
||||
{:name 'Property 3' :value 'b'}]"
|
||||
[properties values]
|
||||
(assert (or (sequential? properties) (nil? properties)))
|
||||
(assert (or (sequential? values) (nil? values)))
|
||||
(let [next-prop-num (next-property-number properties)
|
||||
xf (map-indexed (fn [i v]
|
||||
{:name (str property-prefix (+ next-prop-num i))
|
||||
:value v}))]
|
||||
(into props xf values)))
|
||||
(into properties xf values)))
|
||||
|
||||
(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 as value of a different property. It can add blank properties if
|
||||
necessary, until the min-properties number is reached.
|
||||
|
||||
Example with min-properties=4:
|
||||
|
||||
'Button / Primary / Hover' -> [{:name 'Property 1' :value 'Button'}
|
||||
{:name 'Property 2' :value 'Primary'}
|
||||
{:name 'Property 3' :value 'Hover'}
|
||||
{:name 'Property 4' :value ''}]"
|
||||
([path properties]
|
||||
(path-to-properties path properties 0))
|
||||
([path properties min-props]
|
||||
([path properties min-properties]
|
||||
(assert (or (string? path) (nil? path)))
|
||||
(assert (or (sequential? properties) (nil? properties)))
|
||||
(assert (int? min-properties))
|
||||
(let [cpath (cpn/split-path path)
|
||||
total-props (max (count cpath) min-props)
|
||||
total-properties (max (count cpath) min-properties)
|
||||
assigned (mapv #(assoc % :value (nth cpath %2 "")) properties (range))
|
||||
;; Add empty strings to the end of cpath to reach the minimum number of properties
|
||||
cpath (take total-props (concat cpath (repeat "")))
|
||||
cpath (take total-properties (concat cpath (repeat "")))
|
||||
remaining (drop (count properties) cpath)]
|
||||
(add-new-props assigned remaining))))
|
||||
(add-new-properties assigned remaining))))
|
||||
|
||||
(defn properties-map->formula
|
||||
"Transforms a map of properties to a formula of properties omitting the empty ones"
|
||||
"Transforms a map of properties to a formula of properties omitting the empty ones.
|
||||
|
||||
Example:
|
||||
|
||||
[{:name 'Property 1' :value 'Button'}
|
||||
{:name 'Property 2' :value 'Primary'}] -> 'Property 1=Button, Property 2=Primary'"
|
||||
[properties]
|
||||
(assert (or (sequential? properties) (nil? properties)))
|
||||
(->> properties
|
||||
(keep (fn [{:keys [name value]}]
|
||||
(when (not (str/blank? value))
|
||||
@@ -108,9 +170,15 @@
|
||||
(str/join ", ")))
|
||||
|
||||
(defn properties-formula->map
|
||||
"Transforms a formula of properties to a map of properties"
|
||||
[s]
|
||||
(->> (str/split s ",")
|
||||
"Transforms a formula of properties to a map of properties.
|
||||
|
||||
Example:
|
||||
|
||||
'Property 1=Button, Property 2=Primary' -> [{:name 'Property 1' :value 'Button'}
|
||||
{:name 'Property 2' :value 'Primary'}]"
|
||||
[formula]
|
||||
(assert (or (string? formula) (nil? formula)))
|
||||
(->> (str/split formula ",")
|
||||
(mapv #(str/split % "=" 2))
|
||||
(filter (fn [[_ v]] (not (str/blank? v))))
|
||||
(mapv (fn [[k v]]
|
||||
@@ -118,9 +186,15 @@
|
||||
:value (str/trim v)}))))
|
||||
|
||||
(defn valid-properties-formula?
|
||||
"Checks if a formula is valid"
|
||||
[s]
|
||||
(->> (str/split s ",")
|
||||
"Checks if a formula is valid.
|
||||
|
||||
Example:
|
||||
|
||||
'Property 1=Button, Property 2=Primary' -> true
|
||||
'Property 1=Button, Property 2' -> false"
|
||||
[formula]
|
||||
(assert (or (string? formula) (nil? formula)))
|
||||
(->> (str/split formula ",")
|
||||
(mapv #(str/split % "=" 2))
|
||||
(every? #(and (= 2 (count %))
|
||||
(not (str/blank? (first %)))
|
||||
@@ -128,22 +202,47 @@
|
||||
(< (count (second %)) property-max-length)))))
|
||||
|
||||
(defn find-properties-to-remove
|
||||
"Compares two property maps to find which properties should be removed"
|
||||
[prev-props upd-props]
|
||||
(let [upd-names (set (map :name upd-props))]
|
||||
(filterv #(not (contains? upd-names (:name %))) prev-props)))
|
||||
"Compares two property maps to find which properties should be removed.
|
||||
|
||||
Example:
|
||||
|
||||
[{:name 'Property 1' :value 'x'}
|
||||
{:name 'Property 2' :value 'y'}]
|
||||
[{:name 'Property 1' :value 'x'}] -> [{:name 'Property 2' :value 'y'}]"
|
||||
[prev-properties upd-properties]
|
||||
(assert (or (sequential? prev-properties) (nil? prev-properties)))
|
||||
(assert (or (sequential? upd-properties) (nil? upd-properties)))
|
||||
(let [upd-names (set (map :name upd-properties))]
|
||||
(filterv #(not (contains? upd-names (:name %))) prev-properties)))
|
||||
|
||||
(defn find-properties-to-update
|
||||
"Compares two property maps to find which properties should be updated"
|
||||
[prev-props upd-props]
|
||||
"Compares two property maps to find which properties should be updated.
|
||||
|
||||
Example:
|
||||
|
||||
[{:name 'Property 1' :value 'x'}
|
||||
{:name 'Property 2' :value 'y'}]
|
||||
[{:name 'Property 1' :value 'new-x'}
|
||||
{:name 'Property 2' :value 'y'}] -> [{:name 'Property 1' :value 'new-x'}]"
|
||||
[prev-properties upd-properties]
|
||||
(assert (or (sequential? prev-properties) (nil? prev-properties)))
|
||||
(assert (or (sequential? upd-properties) (nil? upd-properties)))
|
||||
(filterv #(some (fn [prop] (and (= (:name %) (:name prop))
|
||||
(not= (:value %) (:value prop)))) prev-props) upd-props))
|
||||
(not= (:value %) (:value prop)))) prev-properties) upd-properties))
|
||||
|
||||
(defn find-properties-to-add
|
||||
"Compares two property maps to find which properties should be added"
|
||||
[prev-props upd-props]
|
||||
(let [prev-names (set (map :name prev-props))]
|
||||
(filterv #(not (contains? prev-names (:name %))) upd-props)))
|
||||
"Compares two property maps to find which properties should be added.
|
||||
|
||||
Example:
|
||||
|
||||
[{:name 'Property 1' :value 'x'}]
|
||||
[{:name 'Property 1' :value 'x'}
|
||||
{:name 'Property 2' :value 'y'}] -> [{:name 'Property 2' :value 'y'}]"
|
||||
[prev-properties upd-properties]
|
||||
(assert (or (sequential? prev-properties) (nil? prev-properties)))
|
||||
(assert (or (sequential? upd-properties) (nil? upd-properties)))
|
||||
(let [prev-names (set (map :name prev-properties))]
|
||||
(filterv #(not (contains? prev-names (:name %))) upd-properties)))
|
||||
|
||||
(defn- split-base-name-and-number
|
||||
"Extract the number in parentheses from an item, if present, and return both the base name and the number"
|
||||
@@ -165,8 +264,15 @@
|
||||
|
||||
(defn update-number-in-repeated-item
|
||||
"Add, keep or update a number in parentheses for a given item, if necessary, depending on the items
|
||||
already present in a list, to avoid repetitions"
|
||||
already present in a list, to avoid repetitions.
|
||||
|
||||
Example:
|
||||
|
||||
['Property'] 'Property' -> 'Property (1)'
|
||||
['Property' 'Property (1)'] 'Property' -> 'Property (2)'"
|
||||
[items item]
|
||||
(assert (or (sequential? items) (nil? items)))
|
||||
(assert (or (string? item) (nil? item)))
|
||||
(let [names (group-numbers-by-base-name items)
|
||||
[base num] (split-base-name-and-number item)
|
||||
nums-taken (get names base #{})]
|
||||
@@ -176,25 +282,46 @@
|
||||
(str base (when (pos? n) (str " (" n ")")))))))
|
||||
|
||||
(defn update-number-in-repeated-prop-names
|
||||
"Add, keep or update a number for each prop name depending on the previous ones"
|
||||
[props]
|
||||
(->> props
|
||||
"Add, keep or update a number for each prop name depending on the previous ones.
|
||||
|
||||
Example:
|
||||
|
||||
[{:name 'Property' :value 'x'}
|
||||
{:name 'Property' :value 'y'}] -> [{:name 'Property' :value 'x'}
|
||||
{:name 'Property (1)' :value 'y'}]"
|
||||
[properties]
|
||||
(assert (or (sequential? properties) (nil? properties)))
|
||||
(->> properties
|
||||
(reduce (fn [acc prop]
|
||||
(conj acc {:name (update-number-in-repeated-item (mapv :name acc) (:name prop))
|
||||
:value (:value prop)}))
|
||||
[])))
|
||||
|
||||
(defn find-index-for-property-name
|
||||
"Finds the index of a name in a property map"
|
||||
[props name]
|
||||
"Finds the index of a name in a property map.
|
||||
|
||||
Example:
|
||||
|
||||
[{:name 'Property 1' :value 'x'}
|
||||
{:name 'Property 2' :value 'y'}] 'Property 2' -> 1"
|
||||
[properties name]
|
||||
(assert (or (sequential? properties) (nil? properties)))
|
||||
(assert (or (string? name) (nil? name)))
|
||||
(some (fn [[idx prop]]
|
||||
(when (= (:name prop) name)
|
||||
idx))
|
||||
(map-indexed vector props)))
|
||||
(map-indexed vector properties)))
|
||||
|
||||
(defn remove-prefix
|
||||
"Removes the given prefix (with or without a trailing ' / ') from the beginning of the name"
|
||||
"Removes the given prefix (with or without a trailing ' / ') from the beginning of the name.
|
||||
|
||||
Example:
|
||||
|
||||
'Button / Primary' 'Button' -> 'Primary'
|
||||
'Button / Primary' 'Other' -> 'Button / Primary'"
|
||||
[name prefix]
|
||||
(assert (or (string? name) (nil? name)))
|
||||
(assert (or (string? prefix) (nil? prefix)))
|
||||
(let [long-name (str prefix " / ")]
|
||||
(cond
|
||||
(str/starts-with? name long-name)
|
||||
@@ -210,22 +337,22 @@
|
||||
(map :name))
|
||||
|
||||
(defn- matching-indices
|
||||
[props1 props2]
|
||||
(let [names-in-p2 (into #{} xf:map-name props2)
|
||||
[properties1 properties2]
|
||||
(let [names-in-p2 (into #{} xf:map-name properties2)
|
||||
xform (comp
|
||||
(map-indexed (fn [index {:keys [name]}]
|
||||
(when (contains? names-in-p2 name)
|
||||
index)))
|
||||
(filter some?))]
|
||||
(into #{} xform props1)))
|
||||
(into #{} xform properties1)))
|
||||
|
||||
(defn- find-index-by-name
|
||||
"Returns the index of the first item in props with the given name, or nil if not found."
|
||||
[name props]
|
||||
"Returns the index of the first item in properties with the given name, or nil if not found."
|
||||
[name properties]
|
||||
(some (fn [[idx item]]
|
||||
(when (= (:name item) name)
|
||||
idx))
|
||||
(map-indexed vector props)))
|
||||
(map-indexed vector properties)))
|
||||
|
||||
(defn- next-valid-position
|
||||
"Returns the first non-negative integer not present in the used-pos set."
|
||||
@@ -236,42 +363,64 @@
|
||||
p)))
|
||||
|
||||
(defn- find-position
|
||||
"Returns the index of the property with the given name in `props`,
|
||||
"Returns the index of the property with the given name in `properties`,
|
||||
or the next available index not in `used-pos` if not found."
|
||||
[name props used-pos]
|
||||
(or (find-index-by-name name props)
|
||||
[name properties used-pos]
|
||||
(or (find-index-by-name name properties)
|
||||
(next-valid-position used-pos)))
|
||||
|
||||
(defn merge-properties
|
||||
"Merges props2 into props1 with the following rules:
|
||||
- For each property p2 in props2:
|
||||
"Merges properties2 into properties1 with the following rules:
|
||||
- For each property p2 in properties2:
|
||||
- Skip it if its value is empty.
|
||||
- If props1 contains a property with the same name, update its value with that of p2.
|
||||
- Otherwise, assign p2's value to the first unused property in props1. A property is considered used if:
|
||||
- Its name exists in both props1 and props2, or
|
||||
- If properties1 contains a property with the same name, update its value with that of p2.
|
||||
- Otherwise, assign p2's value to the first unused property in properties1. A property is considered used if:
|
||||
- Its name exists in both properties1 and properties2, or
|
||||
- Its value has already been updated during the merge.
|
||||
- If no unused properties are available in props1, append a new property with a default name and p2's value."
|
||||
[props1 props2]
|
||||
(let [props2 (remove #(str/empty? (:value %)) props2)]
|
||||
- If no unused properties are available in properties1, append a new property with a default name and p2's value.
|
||||
|
||||
Example:
|
||||
|
||||
[{:name 'Property 1' :value 'a'}
|
||||
{:name 'Property 2' :value 'b'}]
|
||||
[{:name 'Property 1' :value 'x'}
|
||||
{:name 'Property 2' :value 'y'}
|
||||
{:name 'Property 3' :value 'z'}] -> [{:name 'Property 1' :value 'x'}
|
||||
{:name 'Property 2' :value 'y'}
|
||||
{:name 'Property 3' :value 'z'}]"
|
||||
[properties1 properties2]
|
||||
(assert (or (sequential? properties1) (nil? properties1)))
|
||||
(assert (or (sequential? properties2) (nil? properties2)))
|
||||
(let [properties2 (remove #(str/empty? (:value %)) properties2)]
|
||||
(-> (reduce
|
||||
(fn [{:keys [props used-pos]} prop]
|
||||
(let [pos (find-position (:name prop) props used-pos)
|
||||
(fn [{:keys [properties used-pos]} prop]
|
||||
(let [pos (find-position (:name prop) properties used-pos)
|
||||
used-pos (conj used-pos pos)]
|
||||
(if (< pos (count props))
|
||||
{:props (assoc-in (vec props) [pos :value] (:value prop)) :used-pos used-pos}
|
||||
{:props (add-new-prop props (:value prop)) :used-pos used-pos})))
|
||||
{:props (vec props1) :used-pos (matching-indices props1 props2)}
|
||||
props2)
|
||||
:props)))
|
||||
(if (< pos (count properties))
|
||||
{:properties (assoc-in (vec properties) [pos :value] (:value prop)) :used-pos used-pos}
|
||||
{:properties (add-new-property properties (:value prop)) :used-pos used-pos})))
|
||||
{:properties (vec properties1) :used-pos (matching-indices properties1 properties2)}
|
||||
properties2)
|
||||
:properties)))
|
||||
|
||||
(defn compare-properties
|
||||
"Compares vectors of properties keeping the value if it is the same for all
|
||||
or setting a custom value where their values do not coincide"
|
||||
([props-list]
|
||||
(compare-properties props-list nil))
|
||||
or setting a custom value where their values do not coincide.
|
||||
|
||||
([props-list distinct-mark]
|
||||
(let [grouped (group-by :name (apply concat props-list))
|
||||
Example:
|
||||
|
||||
[[{:name 'Property 1' :value 'x'}
|
||||
{:name 'Property 2' :value 'y'}]
|
||||
[{:name 'Property 1' :value 'x'}
|
||||
{:name 'Property 2' :value 'z'}]] -> [{:name 'Property 1' :value 'x'}
|
||||
{:name 'Property 2' :value nil}]"
|
||||
([properties-list]
|
||||
(compare-properties properties-list nil))
|
||||
|
||||
([properties-list distinct-mark]
|
||||
(assert (or (sequential? properties-list) (nil? properties-list)))
|
||||
(assert (or (string? distinct-mark) (nil? distinct-mark)))
|
||||
(let [grouped (group-by :name (apply concat properties-list))
|
||||
check-values (fn [values]
|
||||
(let [vals (map :value values)]
|
||||
(if (apply = vals)
|
||||
@@ -281,33 +430,37 @@
|
||||
{:name name :value (check-values values)})
|
||||
grouped))))
|
||||
|
||||
(defn same-variant?
|
||||
"Determines if all elements belong to the same variant"
|
||||
[components]
|
||||
(let [variant-ids (distinct (map :variant-id components))
|
||||
not-blank? (complement str/blank?)]
|
||||
(and
|
||||
(= 1 (count variant-ids))
|
||||
(not-blank? (first variant-ids)))))
|
||||
(defn properties-distance
|
||||
"Computes a weighted distance between two property lists `properties1` and `properties2`.
|
||||
Latter properties weight less that previous ones.
|
||||
|
||||
(defn distance
|
||||
"Computes a weighted distance between two property lists `props1` and `props2`.
|
||||
Latter properties weight less that previous ones"
|
||||
[props1 props2]
|
||||
(let [total-num-props (count props1)
|
||||
Example:
|
||||
|
||||
[{:name 'type' :value 'primary'}
|
||||
{:name 'status' :value 'default'}]
|
||||
[{:name 'type' :value 'primary'}
|
||||
{:name 'status' :value 'hover'}] -> 1.0"
|
||||
[properties1 properties2]
|
||||
(assert (or (sequential? properties1) (nil? properties1)))
|
||||
(assert (or (sequential? properties2) (nil? properties2)))
|
||||
(let [total-num-properties (count properties1)
|
||||
xform (map-indexed
|
||||
(fn [idx [p1 p2]]
|
||||
(if (not= p1 p2)
|
||||
(math/pow 2 (- total-num-props idx))
|
||||
(math/pow 2 (- total-num-properties idx))
|
||||
0)))]
|
||||
(transduce
|
||||
xform
|
||||
+
|
||||
(map vector props1 props2))))
|
||||
(map vector properties1 properties2))))
|
||||
|
||||
(defn variant-name-to-name
|
||||
"Transforms a variant-name (its properties values) into a standard name:
|
||||
the real name of the shape joined by the properties values separated by '/'"
|
||||
the real name of the shape joined by the properties values separated by '/'.
|
||||
|
||||
Example:
|
||||
|
||||
{:name 'Button' :variant-name 'Primary, Hover'} -> 'Button / Primary / Hover'"
|
||||
[variant]
|
||||
(cpn/merge-path-item (:name variant) (str/replace (:variant-name variant) #", " " / ")))
|
||||
|
||||
@@ -317,8 +470,13 @@
|
||||
["true" "false"]])
|
||||
|
||||
(defn find-boolean-pair
|
||||
"Given a vector, return a map that contains the boolean equivalency if the values match
|
||||
with any of the boolean pairs. Returns nil if none match."
|
||||
"Given a collection, return a map that contains the boolean equivalency if the values match
|
||||
with any of the boolean pairs. Returns nil if none match.
|
||||
|
||||
Example:
|
||||
|
||||
['on' 'off'] -> {'on' true 'off' false}
|
||||
['foo' 'bar'] -> nil"
|
||||
[[a b :as v]]
|
||||
(let [a' (-> a str/trim str/lower)
|
||||
b' (-> b str/trim str/lower)]
|
||||
@@ -330,3 +488,18 @@
|
||||
(= a' f)) {b true a false}
|
||||
:else nil))
|
||||
boolean-pairs))))
|
||||
|
||||
(defn same-variant?
|
||||
"Determines if all elements belong to the same variant.
|
||||
|
||||
Example:
|
||||
|
||||
[{:variant-id 'abc'} {:variant-id 'abc'}] -> true
|
||||
[{:variant-id 'abc'} {:variant-id 'def'}] -> false"
|
||||
[components]
|
||||
(assert (or (sequential? components) (nil? components)))
|
||||
(let [variant-ids (distinct (map :variant-id components))
|
||||
not-blank? (complement str/blank?)]
|
||||
(and
|
||||
(= 1 (count variant-ids))
|
||||
(not-blank? (first variant-ids)))))
|
||||
@@ -0,0 +1,165 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
|
||||
(ns common-tests.files.variant-test
|
||||
(:require
|
||||
[app.common.files.variant :as fv]
|
||||
[app.common.test-helpers.components :as thc]
|
||||
[app.common.test-helpers.compositions :as tho]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.variants :as thv]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
;; ============================================================
|
||||
;; find-variant-components
|
||||
;; ============================================================
|
||||
|
||||
(t/deftest find-variant-components-empty
|
||||
(let [file (thf/sample-file :file1)
|
||||
data (:data file)
|
||||
page (thf/current-page file)
|
||||
objects (:objects page)]
|
||||
(t/is (= (fv/find-variant-components data (uuid/next))
|
||||
[]))
|
||||
(t/is (= (fv/find-variant-components data objects (uuid/next))
|
||||
[]))))
|
||||
|
||||
(t/deftest find-variant-components-non-variant
|
||||
(let [file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component :c01 :m01 :s01))
|
||||
data (:data file)
|
||||
page (thf/current-page file)
|
||||
objects (:objects page)]
|
||||
(t/is (= (fv/find-variant-components data (thi/id :m01))
|
||||
[]))
|
||||
(t/is (= (fv/find-variant-components data objects (thi/id :m01))
|
||||
[]))))
|
||||
|
||||
(t/deftest find-variant-components-normal
|
||||
(let [file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
|
||||
data (:data file)
|
||||
page (thf/current-page file)
|
||||
objects (:objects page)
|
||||
result (fv/find-variant-components data objects (thi/id :v01))]
|
||||
(t/is (= (count result) 2))
|
||||
(t/is (every? #(contains? % :id) result))
|
||||
(t/is (every? #(contains? % :variant-id) result))))
|
||||
|
||||
(t/deftest find-variant-components-single-variant
|
||||
(let [file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
|
||||
data (:data file)
|
||||
page (thf/current-page file)
|
||||
objects (:objects page)
|
||||
result (fv/find-variant-components data objects (thi/id :v01))]
|
||||
;; Verify the order is maintained (reversed from shapes order)
|
||||
(t/is (= (:variant-id (first result)) (thi/id :v01)))
|
||||
(t/is (= (:variant-id (second result)) (thi/id :v01)))))
|
||||
|
||||
;; ============================================================
|
||||
;; extract-properties-values
|
||||
;; ============================================================
|
||||
|
||||
(t/deftest extract-properties-values-empty
|
||||
(let [file (thf/sample-file :file1)
|
||||
data (:data file)
|
||||
page (thf/current-page file)
|
||||
objects (:objects page)]
|
||||
(t/is (= (fv/extract-properties-values data objects (uuid/next))
|
||||
[]))))
|
||||
|
||||
(t/deftest extract-properties-values-non-variant
|
||||
(let [file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component :c01 :m01 :s01))
|
||||
data (:data file)
|
||||
page (thf/current-page file)
|
||||
objects (:objects page)]
|
||||
(t/is (= (fv/extract-properties-values data objects (thi/id :m01))
|
||||
[]))))
|
||||
|
||||
(t/deftest extract-properties-values-normal
|
||||
(let [file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
|
||||
data (:data file)
|
||||
page (thf/current-page file)
|
||||
objects (:objects page)
|
||||
result (fv/extract-properties-values data objects (thi/id :v01))]
|
||||
(t/is (seq result))
|
||||
(t/is (every? #(contains? % :name) result))
|
||||
(t/is (every? #(contains? % :value) result))
|
||||
(t/is (= (:name (first result)) "Property 1"))
|
||||
(t/is (= (set (:value (first result))) #{"Value1" "Value2"}))))
|
||||
|
||||
(t/deftest extract-properties-values-two-properties
|
||||
(let [file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant-two-properties :v01 :c01 :m01 :c02 :m02))
|
||||
data (:data file)
|
||||
page (thf/current-page file)
|
||||
objects (:objects page)
|
||||
result (fv/extract-properties-values data objects (thi/id :v01))]
|
||||
(t/is (= (count result) 2))
|
||||
(t/is (= (set (map :name result)) #{"Property 1" "Property 2"}))))
|
||||
|
||||
;; ============================================================
|
||||
;; is-secondary-variant?
|
||||
;; ============================================================
|
||||
|
||||
(t/deftest is-secondary-variant-primary
|
||||
(let [file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
|
||||
data (:data file)
|
||||
component (thc/get-component file :c01)]
|
||||
(t/is (not (fv/is-secondary-variant? data component)))))
|
||||
|
||||
(t/deftest is-secondary-variant-secondary
|
||||
(let [file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
|
||||
data (:data file)
|
||||
component (thc/get-component file :c02)]
|
||||
(t/is (fv/is-secondary-variant? data component))))
|
||||
|
||||
(t/deftest is-secondary-variant-not-variant
|
||||
(let [file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component :c01 :m01 :s01))
|
||||
data (:data file)
|
||||
component (thc/get-component file :c01)]
|
||||
(t/is (not (fv/is-secondary-variant? data component)))))
|
||||
|
||||
(t/deftest is-secondary-variant-no-shapes
|
||||
(let [file (thf/sample-file :file1)
|
||||
data (:data file)
|
||||
component {:id :comp :variant-id (thi/id :file1) :main-instance-page (thi/id :file1)}]
|
||||
(t/is (not (fv/is-secondary-variant? data component)))))
|
||||
|
||||
;; ============================================================
|
||||
;; get-primary-variant
|
||||
;; ============================================================
|
||||
|
||||
(t/deftest get-primary-variant-nil
|
||||
(let [file (thf/sample-file :file1)
|
||||
data (:data file)]
|
||||
(t/is (nil? (fv/get-primary-variant data nil)))))
|
||||
|
||||
(t/deftest get-primary-variant-empty
|
||||
(let [file (thf/sample-file :file1)
|
||||
data (:data file)
|
||||
component {:id :comp :variant-id (thi/id :file1) :main-instance-page (thi/id :file1)}]
|
||||
(t/is (nil? (fv/get-primary-variant data component)))))
|
||||
|
||||
(t/deftest get-primary-variant-normal
|
||||
(let [file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
|
||||
data (:data file)
|
||||
component (thc/get-component file :c01)
|
||||
result (fv/get-primary-variant data component)]
|
||||
(t/is (some? result))
|
||||
(t/is (contains? result :id))
|
||||
(t/is (contains? result :component-id))))
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns common-tests.files-migrations-0026-test
|
||||
(:require
|
||||
|
||||
@@ -1,275 +0,0 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
|
||||
(ns common-tests.geom-image-bounds-resize-test
|
||||
(:require
|
||||
#?(:clj [clojure.test :refer [deftest is testing]]
|
||||
:cljs [cljs.test :refer-macros [deftest is testing]])
|
||||
[app.common.math :as mth]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.color :as clr]
|
||||
[app.common.types.fills :as fills]
|
||||
[app.common.types.fills.impl :as fills.impl]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(deftest test-image-transform-schema
|
||||
(testing "validates image with transform"
|
||||
(let [img {:id (uuid/custom 1)
|
||||
:width 400
|
||||
:height 300
|
||||
:mtype "image/png"
|
||||
:keep-aspect-ratio true
|
||||
:transform {:x 0.1 :y -0.2 :width 1.5 :height 2.0}}]
|
||||
(is (sm/validate clr/schema:image img))))
|
||||
|
||||
(testing "validates image without transform"
|
||||
(let [img {:id (uuid/custom 1)
|
||||
:width 400
|
||||
:height 300
|
||||
:mtype "image/png"
|
||||
:keep-aspect-ratio true}]
|
||||
(is (sm/validate clr/schema:image img))))
|
||||
|
||||
(testing "validates fill with image transform"
|
||||
(let [fill {:fill-opacity 0.8
|
||||
:fill-image {:id (uuid/custom 1)
|
||||
:width 400
|
||||
:height 300
|
||||
:mtype "image/png"
|
||||
:keep-aspect-ratio true
|
||||
:transform {:x -0.5 :y -0.5 :width 2.0 :height 2.0}}}]
|
||||
(is (sm/validate fills/schema:fill fill)))))
|
||||
|
||||
(deftest test-image-fill-buffer-roundtrip
|
||||
(testing "roundtrip image fill without transform"
|
||||
(let [fill-vec [{:fill-opacity 0.9
|
||||
:fill-image {:id (uuid/custom 1)
|
||||
:width 800
|
||||
:height 600
|
||||
:mtype "image/jpeg"
|
||||
:keep-aspect-ratio true
|
||||
:name "sample"}}]
|
||||
coerced (fills/from-plain fill-vec)
|
||||
plain (into [] coerced)]
|
||||
(is (= 1 (count plain)))
|
||||
(is (= 0.9 (:fill-opacity (first plain))))
|
||||
(is (= 800 (-> plain first :fill-image :width)))
|
||||
(is (= 600 (-> plain first :fill-image :height)))
|
||||
(is (true? (-> plain first :fill-image :keep-aspect-ratio)))
|
||||
(is (nil? (-> plain first :fill-image :transform)))))
|
||||
|
||||
(testing "roundtrip image fill with transform"
|
||||
(let [fill-vec [{:fill-opacity 0.75
|
||||
:fill-image {:id (uuid/custom 2)
|
||||
:width 1920
|
||||
:height 1080
|
||||
:mtype "image/webp"
|
||||
:keep-aspect-ratio false
|
||||
:name "sample"
|
||||
:transform {:x 0.25 :y -0.15 :width 1.5 :height 2.0}}}]
|
||||
coerced (fills/from-plain fill-vec)
|
||||
plain (into [] coerced)
|
||||
tf (-> plain first :fill-image :transform)]
|
||||
(is (= 1 (count plain)))
|
||||
(is (= 0.75 (:fill-opacity (first plain))))
|
||||
(is (= 1920 (-> plain first :fill-image :width)))
|
||||
(is (= 1080 (-> plain first :fill-image :height)))
|
||||
(is (false? (-> plain first :fill-image :keep-aspect-ratio)))
|
||||
(is (some? tf))
|
||||
(is (mth/close? 0.25 (double (:x tf))))
|
||||
(is (mth/close? -0.15 (double (:y tf))))
|
||||
(is (mth/close? 1.5 (double (:width tf))))
|
||||
(is (mth/close? 2.0 (double (:height tf)))))))
|
||||
|
||||
(defn compute-bounds-resize-transform
|
||||
"Mathematical model for independent image bounds resizing"
|
||||
[{:keys [width height handler center? sx sy transform]}]
|
||||
(let [w-new (* width sx)
|
||||
h-new (* height sy)
|
||||
[dx dy] (if ^boolean center?
|
||||
[(/ (* width (- 1.0 sx)) 2.0)
|
||||
(/ (* height (- 1.0 sy)) 2.0)]
|
||||
[(case handler
|
||||
(:left :bottom-left :top-left) (* width (- 1.0 sx))
|
||||
0.0)
|
||||
(case handler
|
||||
(:top :top-left :top-right) (* height (- 1.0 sy))
|
||||
0.0)])
|
||||
nx0 (get transform :x 0.0)
|
||||
ny0 (get transform :y 0.0)
|
||||
nw0 (get transform :width 1.0)
|
||||
nh0 (get transform :height 1.0)
|
||||
nx' (/ (- (* nx0 width) dx) w-new)
|
||||
ny' (/ (- (* ny0 height) dy) h-new)
|
||||
nw' (/ nw0 sx)
|
||||
nh' (/ nh0 sy)]
|
||||
{:transform {:x nx' :y ny' :width nw' :height nh'}
|
||||
:rendered-pixel-rect {:x (* nx' w-new)
|
||||
:y (* ny' h-new)
|
||||
:width (* nw' w-new)
|
||||
:height (* nh' h-new)}}))
|
||||
|
||||
(deftest test-handle-anchoring-mathematics
|
||||
(testing "Right handle crop (shrinking width to 50%)"
|
||||
(let [res (compute-bounds-resize-transform
|
||||
{:width 200 :height 100 :handler :right :center? false :sx 0.5 :sy 1.0})]
|
||||
(is (mth/close? 0.0 (-> res :transform :x)))
|
||||
(is (mth/close? 0.0 (-> res :transform :y)))
|
||||
(is (mth/close? 2.0 (-> res :transform :width)))
|
||||
(is (mth/close? 1.0 (-> res :transform :height)))
|
||||
;; Rendered pixel content remains 200x100 starting at (0, 0)
|
||||
(is (mth/close? 0.0 (-> res :rendered-pixel-rect :x)))
|
||||
(is (mth/close? 0.0 (-> res :rendered-pixel-rect :y)))
|
||||
(is (mth/close? 200.0 (-> res :rendered-pixel-rect :width)))
|
||||
(is (mth/close? 100.0 (-> res :rendered-pixel-rect :height)))))
|
||||
|
||||
(testing "Left handle crop (shrinking width to 50% from left)"
|
||||
(let [res (compute-bounds-resize-transform
|
||||
{:width 200 :height 100 :handler :left :center? false :sx 0.5 :sy 1.0})]
|
||||
(is (mth/close? -1.0 (-> res :transform :x)))
|
||||
(is (mth/close? 0.0 (-> res :transform :y)))
|
||||
(is (mth/close? 2.0 (-> res :transform :width)))
|
||||
(is (mth/close? 1.0 (-> res :transform :height)))
|
||||
;; Rendered pixel content has left at -100, width 200 -> right edge at +100 (matches right edge of 100px container!)
|
||||
(is (mth/close? -100.0 (-> res :rendered-pixel-rect :x)))
|
||||
(is (mth/close? 200.0 (-> res :rendered-pixel-rect :width)))))
|
||||
|
||||
(testing "Top handle crop (shrinking height to 50% from top)"
|
||||
(let [res (compute-bounds-resize-transform
|
||||
{:width 200 :height 100 :handler :top :center? false :sx 1.0 :sy 0.5})]
|
||||
(is (mth/close? 0.0 (-> res :transform :x)))
|
||||
(is (mth/close? -1.0 (-> res :transform :y)))
|
||||
(is (mth/close? 1.0 (-> res :transform :width)))
|
||||
(is (mth/close? 2.0 (-> res :transform :height)))
|
||||
;; Rendered pixel content has top at -50, height 100 -> bottom edge at +50 (matches bottom edge of 50px container!)
|
||||
(is (mth/close? -50.0 (-> res :rendered-pixel-rect :y)))
|
||||
(is (mth/close? 100.0 (-> res :rendered-pixel-rect :height)))))
|
||||
|
||||
(testing "Top-Left handle crop (shrinking both dimensions to 50%)"
|
||||
(let [res (compute-bounds-resize-transform
|
||||
{:width 200 :height 100 :handler :top-left :center? false :sx 0.5 :sy 0.5})]
|
||||
(is (mth/close? -1.0 (-> res :transform :x)))
|
||||
(is (mth/close? -1.0 (-> res :transform :y)))
|
||||
(is (mth/close? 2.0 (-> res :transform :width)))
|
||||
(is (mth/close? 2.0 (-> res :transform :height)))
|
||||
(is (mth/close? -100.0 (-> res :rendered-pixel-rect :x)))
|
||||
(is (mth/close? -50.0 (-> res :rendered-pixel-rect :y)))
|
||||
(is (mth/close? 200.0 (-> res :rendered-pixel-rect :width)))
|
||||
(is (mth/close? 100.0 (-> res :rendered-pixel-rect :height)))))
|
||||
|
||||
(testing "Center resize (Alt modifier)"
|
||||
(let [res (compute-bounds-resize-transform
|
||||
{:width 200 :height 100 :handler :right :center? true :sx 0.5 :sy 0.5})]
|
||||
(is (mth/close? -0.5 (-> res :transform :x)))
|
||||
(is (mth/close? -0.5 (-> res :transform :y)))
|
||||
(is (mth/close? 2.0 (-> res :transform :width)))
|
||||
(is (mth/close? 2.0 (-> res :transform :height)))
|
||||
(is (mth/close? -50.0 (-> res :rendered-pixel-rect :x)))
|
||||
(is (mth/close? -25.0 (-> res :rendered-pixel-rect :y)))
|
||||
(is (mth/close? 200.0 (-> res :rendered-pixel-rect :width)))
|
||||
(is (mth/close? 100.0 (-> res :rendered-pixel-rect :height)))))
|
||||
|
||||
(testing "Bottom handle crop (shrinking height to 50% from bottom)"
|
||||
(let [res (compute-bounds-resize-transform
|
||||
{:width 200 :height 100 :handler :bottom :center? false :sx 1.0 :sy 0.5})]
|
||||
(is (mth/close? 0.0 (-> res :transform :x)))
|
||||
(is (mth/close? 0.0 (-> res :transform :y)))
|
||||
(is (mth/close? 1.0 (-> res :transform :width)))
|
||||
(is (mth/close? 2.0 (-> res :transform :height)))
|
||||
(is (mth/close? 0.0 (-> res :rendered-pixel-rect :y)))
|
||||
(is (mth/close? 100.0 (-> res :rendered-pixel-rect :height)))))
|
||||
|
||||
(testing "Top-Right handle crop (shrinking both dimensions to 50%)"
|
||||
(let [res (compute-bounds-resize-transform
|
||||
{:width 200 :height 100 :handler :top-right :center? false :sx 0.5 :sy 0.5})]
|
||||
(is (mth/close? 0.0 (-> res :transform :x)))
|
||||
(is (mth/close? -1.0 (-> res :transform :y)))
|
||||
(is (mth/close? 2.0 (-> res :transform :width)))
|
||||
(is (mth/close? 2.0 (-> res :transform :height)))
|
||||
(is (mth/close? 0.0 (-> res :rendered-pixel-rect :x)))
|
||||
(is (mth/close? -50.0 (-> res :rendered-pixel-rect :y)))
|
||||
(is (mth/close? 200.0 (-> res :rendered-pixel-rect :width)))
|
||||
(is (mth/close? 100.0 (-> res :rendered-pixel-rect :height)))))
|
||||
|
||||
(testing "Bottom-Left handle crop (shrinking both dimensions to 50%)"
|
||||
(let [res (compute-bounds-resize-transform
|
||||
{:width 200 :height 100 :handler :bottom-left :center? false :sx 0.5 :sy 0.5})]
|
||||
(is (mth/close? -1.0 (-> res :transform :x)))
|
||||
(is (mth/close? 0.0 (-> res :transform :y)))
|
||||
(is (mth/close? 2.0 (-> res :transform :width)))
|
||||
(is (mth/close? 2.0 (-> res :transform :height)))
|
||||
(is (mth/close? -100.0 (-> res :rendered-pixel-rect :x)))
|
||||
(is (mth/close? 0.0 (-> res :rendered-pixel-rect :y)))
|
||||
(is (mth/close? 200.0 (-> res :rendered-pixel-rect :width)))
|
||||
(is (mth/close? 100.0 (-> res :rendered-pixel-rect :height)))))
|
||||
|
||||
(testing "Expanding bounds beyond original size (empty space exposure)"
|
||||
(let [res (compute-bounds-resize-transform
|
||||
{:width 200 :height 100 :handler :right :center? false :sx 2.0 :sy 1.0})]
|
||||
(is (mth/close? 0.0 (-> res :transform :x)))
|
||||
(is (mth/close? 0.0 (-> res :transform :y)))
|
||||
(is (mth/close? 0.5 (-> res :transform :width)))
|
||||
(is (mth/close? 1.0 (-> res :transform :height)))
|
||||
;; Rendered pixel content is 200px wide in a 400px container -> exposes 200px empty space
|
||||
(is (mth/close? 0.0 (-> res :rendered-pixel-rect :x)))
|
||||
(is (mth/close? 200.0 (-> res :rendered-pixel-rect :width))))))
|
||||
|
||||
(deftest test-sequential-resize-operations
|
||||
(testing "Sequential crops: crop right then crop left"
|
||||
;; Initial shape: 200x100, transform: {:x 0 :y 0 :width 1 :height 1}
|
||||
;; Step 1: Crop right handle from 200 to 150 (sx = 0.75)
|
||||
(let [step1 (compute-bounds-resize-transform
|
||||
{:width 200 :height 100 :handler :right :center? false :sx 0.75 :sy 1.0})
|
||||
tf1 (:transform step1)]
|
||||
(is (mth/close? 0.0 (:x tf1)))
|
||||
(is (mth/close? (/ 1.0 0.75) (:width tf1)))
|
||||
;; Step 2: Now shape is 150x100 with tf1. Crop left handle from 150 to 100 (sx = 100/150 = 2/3)
|
||||
(let [step2 (compute-bounds-resize-transform
|
||||
{:width 150 :height 100 :handler :left :center? false :sx (/ 2.0 3.0) :sy 1.0 :transform tf1})
|
||||
tf2 (:transform step2)]
|
||||
;; The final 100x100 container has bitmap with width 200px
|
||||
(is (mth/close? 200.0 (-> step2 :rendered-pixel-rect :width)))
|
||||
;; The bitmap left edge is at -50px in the 100px container, so right edge is at -50 + 200 = 150px
|
||||
(is (mth/close? -50.0 (-> step2 :rendered-pixel-rect :x))))))
|
||||
|
||||
(testing "Bounds resize followed by standard proportional scaling"
|
||||
;; Step 1: Bounds resize crops width from 200 to 100
|
||||
(let [step1 (compute-bounds-resize-transform
|
||||
{:width 200 :height 100 :handler :right :center? false :sx 0.5 :sy 1.0})
|
||||
tf1 (:transform step1)]
|
||||
(is (mth/close? 2.0 (:width tf1)))
|
||||
(is (mth/close? 1.0 (:height tf1)))
|
||||
|
||||
;; Step 2: Standard proportional scale of the 100x100 cropped shape to 200x200 (scale 2x)
|
||||
;; During standard scale, normalized transform tf1 is kept constant!
|
||||
(let [scaled-w (* 100.0 2.0)
|
||||
scaled-h (* 100.0 2.0)
|
||||
rendered-w (* (:width tf1) scaled-w)
|
||||
rendered-h (* (:height tf1) scaled-h)]
|
||||
;; The underlying bitmap scaled from 200x100 to 400x200, matching the 2x scale of the cropped frame!
|
||||
(is (mth/close? 400.0 rendered-w))
|
||||
(is (mth/close? 200.0 rendered-h))))))
|
||||
|
||||
(deftest test-proportion-lock-invariance
|
||||
(testing "Shape proportion-lock attribute remains unchanged"
|
||||
(let [shape {:id (uuid/custom 10)
|
||||
:type :rect
|
||||
:width 200
|
||||
:height 100
|
||||
:proportion-lock true
|
||||
:fills [{:fill-image {:id (uuid/custom 1)
|
||||
:width 800
|
||||
:height 600
|
||||
:keep-aspect-ratio true}}]}
|
||||
;; Simulate bounds resize interaction
|
||||
has-img? (boolean (or (some :fill-image (:fills shape)) (:fill-image shape)))
|
||||
mod-pressed? true
|
||||
bounds-resize? (and has-img? mod-pressed?)
|
||||
lock-during-drag (if bounds-resize? false (:proportion-lock shape))]
|
||||
;; During drag, lock is bypassed (unless Shift is pressed)
|
||||
(is (false? lock-during-drag))
|
||||
;; Shape's persistent setting is completely preserved
|
||||
(is (true? (:proportion-lock shape))))))
|
||||
@@ -23,13 +23,13 @@
|
||||
[common-tests.files-migrations-test]
|
||||
[common-tests.files.shapes-builder-test]
|
||||
[common-tests.files.validate-test]
|
||||
[common-tests.files.variant-test]
|
||||
[common-tests.geom-align-test]
|
||||
[common-tests.geom-bounds-layout-nil-test]
|
||||
[common-tests.geom-bounds-map-test]
|
||||
[common-tests.geom-flex-layout-test]
|
||||
[common-tests.geom-grid-layout-test]
|
||||
[common-tests.geom-grid-test]
|
||||
[common-tests.geom-image-bounds-resize-test]
|
||||
[common-tests.geom-line-test]
|
||||
[common-tests.geom-modif-tree-test]
|
||||
[common-tests.geom-modifiers-test]
|
||||
@@ -88,6 +88,7 @@
|
||||
[common-tests.types.token-test]
|
||||
[common-tests.types.tokens-lib-test]
|
||||
[common-tests.types.tokens-status-test]
|
||||
[common-tests.types.variant-test]
|
||||
[common-tests.undo-stack-test]
|
||||
[common-tests.uuid-test]))
|
||||
|
||||
@@ -103,13 +104,13 @@
|
||||
'common-tests.files-migrations-0026-test
|
||||
'common-tests.files-migrations-test
|
||||
'common-tests.files.validate-test
|
||||
'common-tests.files.variant-test
|
||||
'common-tests.geom-align-test
|
||||
'common-tests.geom-bounds-layout-nil-test
|
||||
'common-tests.geom-bounds-map-test
|
||||
'common-tests.geom-flex-layout-test
|
||||
'common-tests.geom-grid-layout-test
|
||||
'common-tests.geom-grid-test
|
||||
'common-tests.geom-image-bounds-resize-test
|
||||
'common-tests.geom-line-test
|
||||
'common-tests.geom-modif-tree-test
|
||||
'common-tests.geom-modifiers-test
|
||||
@@ -144,6 +145,7 @@
|
||||
'common-tests.logic.token-test
|
||||
'common-tests.logic.variants-switch-test
|
||||
'common-tests.math-test
|
||||
'common-tests.types.variant-test
|
||||
'common-tests.media-test
|
||||
'common-tests.path-names-test
|
||||
'common-tests.record-test
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns common-tests.types.tokens-status-test
|
||||
(:require
|
||||
|
||||
@@ -7,10 +7,238 @@
|
||||
(ns common-tests.types.variant-test
|
||||
(:require
|
||||
[app.common.types.variant :as ctv]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/deftest variant-component
|
||||
(t/is (not (ctv/variant-component? nil)))
|
||||
(t/is (not (ctv/variant-component? {})))
|
||||
(t/is (ctv/variant-component? {:variant-id (uuid/next)})))
|
||||
|
||||
(t/deftest variant-distance01
|
||||
(t/deftest variant-shape
|
||||
(t/is (not (ctv/variant-shape? nil)))
|
||||
(t/is (not (ctv/variant-shape? {})))
|
||||
(t/is (ctv/variant-shape? {:variant-id (uuid/next)})))
|
||||
|
||||
(t/deftest variant-container
|
||||
(t/is (not (ctv/variant-container? nil)))
|
||||
(t/is (not (ctv/variant-container? {})))
|
||||
(t/is (ctv/variant-container? {:is-variant-container true})))
|
||||
|
||||
(t/deftest properties-to-name-test
|
||||
(t/is (= "" (ctv/properties-to-name [])))
|
||||
(t/is (= "" (ctv/properties-to-name nil)))
|
||||
(t/is (= "Button, Primary" (ctv/properties-to-name [{:name "Property 1" :value "Button"}
|
||||
{:name "Property 2" :value "Primary"}])))
|
||||
(t/is (= "Button" (ctv/properties-to-name [{:name "Property 1" :value "Button"}
|
||||
{:name "Property 2" :value ""}]))))
|
||||
|
||||
(t/deftest next-property-number-test
|
||||
(t/is (= 1 (ctv/next-property-number [])))
|
||||
(t/is (= 1 (ctv/next-property-number nil)))
|
||||
(t/is (= 2 (ctv/next-property-number [{:name "Property 1" :value "x"}])))
|
||||
(t/is (= 4 (ctv/next-property-number [{:name "Property 3" :value "x"}])))
|
||||
(t/is (= 3 (ctv/next-property-number [{:name "Property 1" :value "x"}
|
||||
{:name "Property 2" :value "y"}]))))
|
||||
|
||||
(t/deftest add-new-property-test
|
||||
(t/is (= [{:name "Property 1" :value "x"}]
|
||||
(ctv/add-new-property [] "x")))
|
||||
(t/is (= [{:name "Property 1" :value "x"}]
|
||||
(ctv/add-new-property nil "x")))
|
||||
(t/is (= [{:name "Property 1" :value "x"} {:name "Property 2" :value "y"}]
|
||||
(ctv/add-new-property [{:name "Property 1" :value "x"}] "y"))))
|
||||
|
||||
(t/deftest add-new-properties-test
|
||||
(t/is (= [{:name "Property 1" :value "a"} {:name "Property 2" :value "b"}]
|
||||
(ctv/add-new-properties [] ["a" "b"])))
|
||||
(t/is (= '({:name "Property 2" :value "b"} {:name "Property 1" :value "a"})
|
||||
(ctv/add-new-properties nil ["a" "b"])))
|
||||
(t/is (= [{:name "Property 1" :value "x"} {:name "Property 2" :value "a"} {:name "Property 3" :value "b"}]
|
||||
(ctv/add-new-properties [{:name "Property 1" :value "x"}] ["a" "b"]))))
|
||||
|
||||
(t/deftest path-to-properties-test
|
||||
(t/is (= [] (ctv/path-to-properties "" [])))
|
||||
(t/is (= [{:name "Property 1" :value "a"} {:name "Property 2" :value "b"}]
|
||||
(ctv/path-to-properties "a / b" nil)))
|
||||
(t/is (= [{:name "Property 1" :value "Button"}
|
||||
{:name "Property 2" :value "Primary"}
|
||||
{:name "Property 3" :value "Hover"}]
|
||||
(ctv/path-to-properties "Button / Primary / Hover" [])))
|
||||
(t/is (= [{:name "Property 1" :value "Button"}
|
||||
{:name "Property 2" :value "Primary"}
|
||||
{:name "Property 3" :value "Hover"}
|
||||
{:name "Property 4" :value ""}]
|
||||
(ctv/path-to-properties "Button / Primary / Hover" [] 4)))
|
||||
(t/is (= [{:name "Property 1" :value "Button"}
|
||||
{:name "Property 2" :value "Primary"}]
|
||||
(ctv/path-to-properties "Button / Primary" [{:name "Property 1" :value "old"}
|
||||
{:name "Property 2" :value "old2"}]))))
|
||||
|
||||
(t/deftest properties-map->formula-test
|
||||
(t/is (= "" (ctv/properties-map->formula [])))
|
||||
(t/is (= "" (ctv/properties-map->formula nil)))
|
||||
(t/is (= "Property 1=Button, Property 2=Primary"
|
||||
(ctv/properties-map->formula [{:name "Property 1" :value "Button"}
|
||||
{:name "Property 2" :value "Primary"}])))
|
||||
(t/is (= "Property 1=Button"
|
||||
(ctv/properties-map->formula [{:name "Property 1" :value "Button"}
|
||||
{:name "Property 2" :value ""}]))))
|
||||
|
||||
(t/deftest properties-formula->map-test
|
||||
(t/is (= [] (ctv/properties-formula->map "")))
|
||||
(t/is (= [] (ctv/properties-formula->map nil)))
|
||||
(t/is (= [{:name "Property 1" :value "Button"} {:name "Property 2" :value "Primary"}]
|
||||
(ctv/properties-formula->map "Property 1=Button, Property 2=Primary")))
|
||||
(t/is (= [{:name "Property 1" :value "Button"}]
|
||||
(ctv/properties-formula->map "Property 1=Button, Property 2="))))
|
||||
|
||||
(t/deftest valid-properties-formula?-test
|
||||
(t/is (= true (ctv/valid-properties-formula? "Property 1=Button, Property 2=Primary")))
|
||||
(t/is (= false (ctv/valid-properties-formula? "")))
|
||||
(t/is (= true (ctv/valid-properties-formula? nil)))
|
||||
(t/is (= false (ctv/valid-properties-formula? "Property 1=Button, Property 2"))))
|
||||
|
||||
(t/deftest find-properties-to-remove-test
|
||||
(t/is (= [] (ctv/find-properties-to-remove [] [])))
|
||||
(t/is (= [] (ctv/find-properties-to-remove nil nil)))
|
||||
(t/is (= [{:name "Property 3" :value "z"}]
|
||||
(ctv/find-properties-to-remove [{:name "Property 1" :value "x"}
|
||||
{:name "Property 2" :value "y"}
|
||||
{:name "Property 3" :value "z"}]
|
||||
[{:name "Property 1" :value "x"}
|
||||
{:name "Property 2" :value "y"}])))
|
||||
(t/is (= [{:name "Property 1" :value "x"} {:name "Property 2" :value "y"}]
|
||||
(ctv/find-properties-to-remove [{:name "Property 1" :value "x"}
|
||||
{:name "Property 2" :value "y"}]
|
||||
[{:name "Property 3" :value "z"}]))))
|
||||
|
||||
(t/deftest find-properties-to-update-test
|
||||
(t/is (= [] (ctv/find-properties-to-update [] [])))
|
||||
(t/is (= [] (ctv/find-properties-to-update nil nil)))
|
||||
(t/is (= [{:name "Property 1" :value "new-x"}]
|
||||
(ctv/find-properties-to-update [{:name "Property 1" :value "x"}
|
||||
{:name "Property 2" :value "y"}]
|
||||
[{:name "Property 1" :value "new-x"}
|
||||
{:name "Property 2" :value "y"}])))
|
||||
(t/is (= [{:name "Property 1" :value "new-x"} {:name "Property 2" :value "new-y"}]
|
||||
(ctv/find-properties-to-update [{:name "Property 1" :value "x"}
|
||||
{:name "Property 2" :value "y"}]
|
||||
[{:name "Property 1" :value "new-x"}
|
||||
{:name "Property 2" :value "new-y"}]))))
|
||||
|
||||
(t/deftest find-properties-to-add-test
|
||||
(t/is (= [] (ctv/find-properties-to-add [] [])))
|
||||
(t/is (= [] (ctv/find-properties-to-add nil nil)))
|
||||
(t/is (= [{:name "Property 3" :value "z"}]
|
||||
(ctv/find-properties-to-add [{:name "Property 1" :value "x"}
|
||||
{:name "Property 2" :value "y"}]
|
||||
[{:name "Property 1" :value "x"}
|
||||
{:name "Property 2" :value "y"}
|
||||
{:name "Property 3" :value "z"}])))
|
||||
(t/is (= [{:name "Property 2" :value "y"}]
|
||||
(ctv/find-properties-to-add [{:name "Property 1" :value "x"}]
|
||||
[{:name "Property 1" :value "x"}
|
||||
{:name "Property 2" :value "y"}]))))
|
||||
|
||||
(t/deftest update-number-in-repeated-item-test
|
||||
(t/is (= "Property" (ctv/update-number-in-repeated-item [] "Property")))
|
||||
(t/is (= "Property" (ctv/update-number-in-repeated-item nil "Property")))
|
||||
(t/is (= "Property (1)" (ctv/update-number-in-repeated-item ["Property"] "Property")))
|
||||
(t/is (= "Property (2)" (ctv/update-number-in-repeated-item ["Property" "Property (1)"] "Property")))
|
||||
(t/is (= "Property" (ctv/update-number-in-repeated-item ["Other"] "Property"))))
|
||||
|
||||
(t/deftest update-number-in-repeated-prop-names-test
|
||||
(t/is (= [] (ctv/update-number-in-repeated-prop-names [])))
|
||||
(t/is (= [] (ctv/update-number-in-repeated-prop-names nil)))
|
||||
(t/is (= [{:name "Property" :value "x"}]
|
||||
(ctv/update-number-in-repeated-prop-names [{:name "Property" :value "x"}])))
|
||||
(t/is (= [{:name "Property" :value "x"} {:name "Property (1)" :value "y"}]
|
||||
(ctv/update-number-in-repeated-prop-names [{:name "Property" :value "x"}
|
||||
{:name "Property" :value "y"}])))
|
||||
(t/is (= [{:name "Property" :value "x"} {:name "Property (1)" :value "y"} {:name "Property (2)" :value "z"}]
|
||||
(ctv/update-number-in-repeated-prop-names [{:name "Property" :value "x"}
|
||||
{:name "Property" :value "y"}
|
||||
{:name "Property" :value "z"}]))))
|
||||
|
||||
(t/deftest find-index-for-property-name-test
|
||||
(t/is (= nil (ctv/find-index-for-property-name [] "Property 1")))
|
||||
(t/is (= nil (ctv/find-index-for-property-name nil "Property 1")))
|
||||
(t/is (= 0 (ctv/find-index-for-property-name [{:name "Property 1" :value "x"}] "Property 1")))
|
||||
(t/is (= 1 (ctv/find-index-for-property-name [{:name "Property 1" :value "x"}
|
||||
{:name "Property 2" :value "y"}] "Property 2")))
|
||||
(t/is (= nil (ctv/find-index-for-property-name [{:name "Property 1" :value "x"}] "Property 3"))))
|
||||
|
||||
(t/deftest remove-prefix-test
|
||||
(t/is (= "name" (ctv/remove-prefix "name" "")))
|
||||
(t/is (= "name" (ctv/remove-prefix "name" nil)))
|
||||
(t/is (= "Primary" (ctv/remove-prefix "Button / Primary" "Button")))
|
||||
(t/is (= "Primary" (ctv/remove-prefix "Button / Primary" "Button / ")))
|
||||
(t/is (= "Button / Primary" (ctv/remove-prefix "Button / Primary" "Other"))))
|
||||
|
||||
(t/deftest merge-properties-test
|
||||
(t/is (= [] (ctv/merge-properties [] [])))
|
||||
(t/is (= [] (ctv/merge-properties nil nil)))
|
||||
(t/is (= [{:name "Property 1" :value "x"} {:name "Property 2" :value "y"}]
|
||||
(ctv/merge-properties [{:name "Property 1" :value "a"}
|
||||
{:name "Property 2" :value "b"}]
|
||||
[{:name "Property 1" :value "x"}
|
||||
{:name "Property 2" :value "y"}])))
|
||||
(t/is (= [{:name "Property 1" :value "x"} {:name "Property 2" :value "y"} {:name "Property 3" :value "z"}]
|
||||
(ctv/merge-properties [{:name "Property 1" :value "a"}
|
||||
{:name "Property 2" :value "b"}]
|
||||
[{:name "Property 1" :value "x"}
|
||||
{:name "Property 2" :value "y"}
|
||||
{:name "Property 3" :value "z"}])))
|
||||
(t/is (= [{:name "Property 1" :value "a"} {:name "Property 2" :value "y"}]
|
||||
(ctv/merge-properties [{:name "Property 1" :value "a"}
|
||||
{:name "Property 2" :value "b"}]
|
||||
[{:name "Property 2" :value "y"}]))))
|
||||
|
||||
(t/deftest compare-properties-test
|
||||
(t/is (= [] (ctv/compare-properties [])))
|
||||
(t/is (= [] (ctv/compare-properties nil)))
|
||||
(t/is (= [{:name "Property 1" :value "x"} {:name "Property 2" :value "y"}]
|
||||
(ctv/compare-properties [[{:name "Property 1" :value "x"}
|
||||
{:name "Property 2" :value "y"}]])))
|
||||
(t/is (= [{:name "Property 1" :value "x"} {:name "Property 2" :value nil}]
|
||||
(ctv/compare-properties [[{:name "Property 1" :value "x"}
|
||||
{:name "Property 2" :value "y"}]
|
||||
[{:name "Property 1" :value "x"}
|
||||
{:name "Property 2" :value "z"}]])))
|
||||
(t/is (= [{:name "Property 1" :value "x"} {:name "Property 2" :value "*"}]
|
||||
(ctv/compare-properties [[{:name "Property 1" :value "x"}
|
||||
{:name "Property 2" :value "y"}]
|
||||
[{:name "Property 1" :value "x"}
|
||||
{:name "Property 2" :value "z"}]]
|
||||
"*"))))
|
||||
|
||||
(t/deftest variant-name-to-name-test
|
||||
(t/is (= "Button / Primary / Hover" (ctv/variant-name-to-name {:name "Button" :variant-name "Primary, Hover"})))
|
||||
(t/is (= "Button" (ctv/variant-name-to-name {:name "Button" :variant-name ""})))
|
||||
(t/is (= "Button" (ctv/variant-name-to-name {:name "Button" :variant-name nil})))
|
||||
(t/is (= "" (ctv/variant-name-to-name {:name "" :variant-name ""})))
|
||||
(t/is (= nil (ctv/variant-name-to-name {:name nil :variant-name nil}))))
|
||||
|
||||
(t/deftest find-boolean-pair-test
|
||||
(t/is (= {"on" true "off" false} (ctv/find-boolean-pair ["on" "off"])))
|
||||
(t/is (= {"yes" true "no" false} (ctv/find-boolean-pair ["yes" "no"])))
|
||||
(t/is (= {"true" true "false" false} (ctv/find-boolean-pair ["true" "false"])))
|
||||
(t/is (= {"on" true "off" false} (ctv/find-boolean-pair ["off" "on"])))
|
||||
(t/is (= {"ON" true "OFF" false} (ctv/find-boolean-pair ["ON" "OFF"])))
|
||||
(t/is (= nil (ctv/find-boolean-pair ["foo" "bar"])))
|
||||
(t/is (= nil (ctv/find-boolean-pair nil)))
|
||||
(t/is (= nil (ctv/find-boolean-pair ["on"]))))
|
||||
|
||||
(t/deftest same-variant?-test
|
||||
(t/is (= false (ctv/same-variant? [])))
|
||||
(t/is (= false (ctv/same-variant? nil)))
|
||||
(t/is (= true (ctv/same-variant? [{:variant-id "abc"}])))
|
||||
(t/is (= true (ctv/same-variant? [{:variant-id "abc"} {:variant-id "abc"}])))
|
||||
(t/is (= false (ctv/same-variant? [{:variant-id "abc"} {:variant-id "def"}])))
|
||||
(t/is (= false (ctv/same-variant? [{:variant-id ""} {:variant-id ""}]))))
|
||||
|
||||
(t/deftest properties-distance01
|
||||
;;c1: primary, default, rounded, blue, dark
|
||||
;;c2: primary, hover, squared, blue, dark
|
||||
;;c3: primary, default, squared, blue, light
|
||||
@@ -35,12 +263,11 @@
|
||||
{:name "borders" :value "rounded"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "theme" :value "light"}]
|
||||
dist2 (ctv/distance target props2)
|
||||
dist3 (ctv/distance target props3)]
|
||||
dist2 (ctv/properties-distance target props2)
|
||||
dist3 (ctv/properties-distance target props3)]
|
||||
(t/is (< dist3 dist2))))
|
||||
|
||||
|
||||
(t/deftest variant-distance02
|
||||
(t/deftest properties-distance02
|
||||
;;c1: primary, default, rounded, blue, dark
|
||||
;;c2: primary, hover, squared, red, dark
|
||||
;;c3: secondary, hover, rounded, blue, dark
|
||||
@@ -65,11 +292,11 @@
|
||||
{:name "borders" :value "rounded"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "theme" :value "dark"}]
|
||||
dist2 (ctv/distance target props2)
|
||||
dist3 (ctv/distance target props3)]
|
||||
dist2 (ctv/properties-distance target props2)
|
||||
dist3 (ctv/properties-distance target props3)]
|
||||
(t/is (< dist2 dist3))))
|
||||
|
||||
(t/deftest variant-distance03
|
||||
(t/deftest properties-distance03
|
||||
;;c1: primary, default, rounded, blue, dark
|
||||
;;c2: secondary, default, rounded, blue, light
|
||||
;;c3: secondary, hover, squared, blue, dark
|
||||
@@ -101,12 +328,18 @@
|
||||
{:name "borders" :value "rounded"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "theme" :value "dark"}]
|
||||
dist2 (ctv/distance target props2)
|
||||
dist3 (ctv/distance target props3)
|
||||
dist4 (ctv/distance target props4)]
|
||||
dist2 (ctv/properties-distance target props2)
|
||||
dist3 (ctv/properties-distance target props3)
|
||||
dist4 (ctv/properties-distance target props4)]
|
||||
(t/is (< dist2 dist4))
|
||||
(t/is (< dist4 dist3))))
|
||||
|
||||
(t/deftest properties-distance04
|
||||
(t/is (= 0 (ctv/properties-distance [] [])))
|
||||
(t/is (= 0 (ctv/properties-distance nil nil)))
|
||||
(t/is (= 0 (ctv/properties-distance [{:name "a" :value "x"}] [{:name "a" :value "x"}])))
|
||||
(t/is (= 2.0 (ctv/properties-distance [{:name "a" :value "x"} {:name "b" :value "y"}] [{:name "a" :value "x"} {:name "b" :value "z"}]))))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.auth
|
||||
"Resolves the caller's session cookie to a real profile id.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.handlers.export
|
||||
"Handle export jobs"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.handlers.jobs
|
||||
"REST surface for export jobs, under `/api/export/jobs`.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.jobs
|
||||
"Export job model and lifecycle.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.jobs.scheduler
|
||||
"Admission control for export jobs.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.jobs.store
|
||||
"Redis persistence for export jobs.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.jobs.utils
|
||||
"Temp file ownership for export jobs.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.router
|
||||
"Method + path dispatch.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.wasm.pool
|
||||
"Pool of headless render workers.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.wasm.render
|
||||
"Headless render pipeline: renders exports with the render-wasm Skia pipeline,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.wasm.worker
|
||||
"Render worker entry point.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns exporter-tests.export-shapes-test
|
||||
"Chunking of the browser backend."
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns exporter-tests.jobs-test
|
||||
"Job state machine. Runs without redis: a store write with no connection is
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns exporter-tests.scheduler-test
|
||||
"Admission control. A headless job leases one render worker for its whole run,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns exporter-tests.wasm-pool-test
|
||||
"Worker leasing, against a stub pool: `with-worker` must give the worker back
|
||||
|
||||
@@ -274,25 +274,6 @@ test("Renders a file with different text leaves decoration", async ({
|
||||
await expect(workspace.canvas).toHaveScreenshot();
|
||||
});
|
||||
|
||||
// Both paragraphs decorate the same spans; the first one paints every span with
|
||||
// the same fill, which used to collapse the decorated spans into their
|
||||
// neighbours and drop their underline / line-through.
|
||||
test("Renders text spans decorated independently of their fill", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspace = new WasmWorkspacePage(page);
|
||||
await workspace.setupEmptyFile();
|
||||
await workspace.mockGetFile("render-wasm/get-file-text-span-decoration.json");
|
||||
|
||||
await workspace.goToWorkspace({
|
||||
id: "1d0f6a4c-0000-8000-8006-000000000001",
|
||||
pageId: "1d0f6a4c-0000-8000-8006-000000000002",
|
||||
});
|
||||
|
||||
await workspace.waitForFirstRenderWithoutUI();
|
||||
await expect(workspace.canvas).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("Renders a file with different text shadows combinations", async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
|
Before Width: | Height: | Size: 299 KiB After Width: | Height: | Size: 241 KiB |
|
Before Width: | Height: | Size: 220 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 471 KiB After Width: | Height: | Size: 450 KiB |
|
Before Width: | Height: | Size: 665 KiB After Width: | Height: | Size: 519 KiB |
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 169 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 249 KiB |
@@ -103,12 +103,14 @@
|
||||
(wasm.api/process-objects shapes)
|
||||
(wasm.api/request-render "sync-wasm-structural-changes"))))))
|
||||
|
||||
(defn- apply-changes-localy
|
||||
(defn- apply-changes-locally
|
||||
[{:keys [file-id redo-changes ignore-wasm?] :as commit} pending]
|
||||
(ptk/reify ::apply-changes-localy
|
||||
(ptk/reify ::apply-changes-locally
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [undo-changes
|
||||
(let [libraries (dsh/lookup-libraries state)
|
||||
|
||||
undo-changes
|
||||
(if pending
|
||||
(->> pending
|
||||
(map :undo-changes)
|
||||
@@ -126,8 +128,8 @@
|
||||
|
||||
apply-changes
|
||||
(fn [fdata]
|
||||
(let [fdata (cpc/process-changes fdata undo-changes false)
|
||||
fdata (cpc/process-changes fdata redo-changes false)
|
||||
(let [fdata (cpc/process-changes fdata undo-changes false libraries)
|
||||
fdata (cpc/process-changes fdata redo-changes false libraries)
|
||||
pids (into #{} xf:map-page-id redo-changes)]
|
||||
(reduce #(ctst/update-object-indices %1 %2) fdata pids)))]
|
||||
|
||||
@@ -200,7 +202,7 @@
|
||||
(let [pending (when-not local?
|
||||
(get-pending-commits state))]
|
||||
(rx/concat
|
||||
(rx/of (apply-changes-localy commit pending))
|
||||
(rx/of (apply-changes-locally commit pending))
|
||||
(if pending
|
||||
(rx/concat
|
||||
(->> (rx/from (reverse pending))
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
[app.main.data.team :as dtm]
|
||||
[app.main.repo :as rp]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.storage :as storage]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
@@ -532,35 +531,6 @@
|
||||
(update [_ state]
|
||||
(update state :comments-local dissoc :expanded))))
|
||||
|
||||
(def ^:private hide-resolved-comments-storage-key
|
||||
:app.main.data.comments/hide-resolved-comments?)
|
||||
|
||||
(defn- load-hide-resolved-comments?
|
||||
[]
|
||||
(= true (get @storage/user hide-resolved-comments-storage-key)))
|
||||
|
||||
(defn- persist-hide-resolved-comments!
|
||||
[hide?]
|
||||
(swap! storage/user assoc hide-resolved-comments-storage-key hide?))
|
||||
|
||||
(defn merge-persisted-filters
|
||||
"Merge persisted hide-resolved preference into comments local state."
|
||||
[local]
|
||||
(let [local (or local {})]
|
||||
(if (contains? local :show)
|
||||
local
|
||||
(assoc local :show (if (load-hide-resolved-comments?)
|
||||
:pending
|
||||
:all)))))
|
||||
|
||||
(defn initialize-comments-filters
|
||||
"Load persisted comment filter preferences into `:comments-local`."
|
||||
[]
|
||||
(ptk/reify ::initialize-comments-filters
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :comments-local merge-persisted-filters))))
|
||||
|
||||
(defn update-filters
|
||||
[{:keys [mode show list] :as params}]
|
||||
(ptk/reify ::update-filters
|
||||
@@ -576,12 +546,7 @@
|
||||
(assoc :show show)
|
||||
|
||||
(some? list)
|
||||
(assoc :list list)))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
(when (some? show)
|
||||
(persist-hide-resolved-comments! (= :pending show))))))
|
||||
(assoc :list list)))))))
|
||||
|
||||
(defn update-options
|
||||
[params]
|
||||
|
||||
@@ -77,8 +77,7 @@
|
||||
(if (nil? lstate)
|
||||
default-local-state
|
||||
lstate)))
|
||||
(assoc-in [:viewer-local :share-id] share-id)
|
||||
(update :comments-local dcmt/merge-persisted-filters)))
|
||||
(assoc-in [:viewer-local :share-id] share-id)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
|
||||
@@ -403,8 +403,7 @@
|
||||
(assoc :recent-fonts (:recent-fonts storage/user))
|
||||
(assoc :current-file-id file-id)
|
||||
(assoc :workspace-presence {})
|
||||
(update :workspace-global dissoc :default-font)
|
||||
(update :comments-local dcmt/merge-persisted-filters)))
|
||||
(update :workspace-global dissoc :default-font)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
@@ -1208,26 +1207,30 @@
|
||||
(ptk/reify ::show-component-in-assets
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [file-id (:current-file-id state)
|
||||
fdata (dsh/lookup-file-data state file-id)
|
||||
component (cfv/get-primary-component fdata component-id)
|
||||
cpath (:path component)
|
||||
cpath (cpn/split-path cpath)
|
||||
paths (map (fn [i] (cpn/join-path (take (inc i) cpath)))
|
||||
(range (count cpath)))]
|
||||
(let [file-id (:current-file-id state)
|
||||
fdata (dsh/lookup-file-data state file-id)
|
||||
component (ctkl/get-component fdata component-id)
|
||||
primary-variant (cfv/get-primary-variant fdata component)
|
||||
primary-component (ctkl/get-component fdata (:component-id primary-variant))
|
||||
cpath (:path primary-component)
|
||||
cpath (cpn/split-path cpath)
|
||||
paths (map (fn [i] (cpn/join-path (take (inc i) cpath)))
|
||||
(range (count cpath)))]
|
||||
(rx/concat
|
||||
(rx/from (map #(set-assets-group-open file-id :components % true) paths))
|
||||
(rx/of (dcm/go-to-workspace :layout :assets)
|
||||
(set-assets-section-open file-id :library true)
|
||||
(set-assets-section-open file-id :components true)
|
||||
(select-single-asset file-id (:id component) :components)))))
|
||||
(select-single-asset file-id (:id primary-component) :components)))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(let [file-id (:current-file-id state)
|
||||
fdata (dsh/lookup-file-data state file-id)
|
||||
component (cfv/get-primary-component fdata component-id)
|
||||
wrapper-id (str "component-shape-id-" (:id component))]
|
||||
(let [file-id (:current-file-id state)
|
||||
fdata (dsh/lookup-file-data state file-id)
|
||||
component (ctkl/get-component fdata component-id)
|
||||
primary-variant (cfv/get-primary-variant fdata component)
|
||||
primary-component (ctkl/get-component fdata (:component-id primary-variant))
|
||||
wrapper-id (str "component-shape-id-" (:id primary-component))]
|
||||
(tm/schedule-on-idle #(dom/scroll-into-view-if-needed! (dom/get-element wrapper-id)))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@@ -333,16 +333,13 @@
|
||||
[:stroke-style
|
||||
:stroke-alignment
|
||||
:stroke-width
|
||||
:stroke-dash
|
||||
:stroke-gap
|
||||
:stroke-per-side
|
||||
:stroke-width-top
|
||||
:stroke-width-right
|
||||
:stroke-width-bottom
|
||||
:stroke-width-left
|
||||
:stroke-cap-start
|
||||
:stroke-cap-end
|
||||
:hidden])
|
||||
:stroke-cap-end])
|
||||
|
||||
;; FIXME: this function initializes an empty stroke, maybe we can move
|
||||
;; it to common.types
|
||||
|
||||
@@ -99,9 +99,7 @@
|
||||
:layout-item-margin-type
|
||||
:layout-grid-cells
|
||||
:layout-grid-columns
|
||||
:layout-grid-rows
|
||||
:fills
|
||||
:fill-image})
|
||||
:layout-grid-rows})
|
||||
|
||||
;; -- temporary modifiers -------------------------------------------
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.main.data.workspace.path.clipboard
|
||||
(:require
|
||||
|
||||
@@ -193,7 +193,8 @@
|
||||
layout-initializer (get-layout-initializer type from-frame? calculate-params?)]
|
||||
|
||||
(rx/of (dwu/start-undo-transaction undo-id)
|
||||
(dwsh/update-shapes [id] layout-initializer {:with-objects? true})
|
||||
(dwsh/update-shapes [id] layout-initializer {:with-objects? true
|
||||
:extra-context (str "create-layout-from-id: " id)})
|
||||
(dwsh/update-shapes (dm/get-prop parent :shapes) #(dissoc % :constraints-h :constraints-v))
|
||||
(ptk/data-event :layout/update {:ids [id]})
|
||||
(dwu/commit-undo-transaction undo-id))))))
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
{:as props
|
||||
:keys [reg-objects? save-undo? stack-undo? attrs ignore-tree page-id
|
||||
ignore-touched undo-group with-objects? changed-sub-attr translation?
|
||||
skip-component-sync?]
|
||||
skip-component-sync? extra-context]
|
||||
:or {reg-objects? false
|
||||
save-undo? true
|
||||
stack-undo? false
|
||||
@@ -222,7 +222,8 @@
|
||||
:ignore-tree ignore-tree
|
||||
:ignore-touched ignore-touched
|
||||
:with-objects? with-objects?
|
||||
:translation? translation?})
|
||||
:translation? translation?
|
||||
:extra-context extra-context})
|
||||
(cond-> undo-group
|
||||
(pcb/set-undo-group undo-group))
|
||||
(pcb/set-translation? translation?)
|
||||
|
||||
@@ -149,15 +149,10 @@
|
||||
|
||||
;; -- Resize --------------------------------------------------------
|
||||
|
||||
(defn- shape-has-image-fill?
|
||||
[shape]
|
||||
(boolean (or (some :fill-image (:fills shape))
|
||||
(:fill-image shape))))
|
||||
|
||||
(defn start-resize
|
||||
"Enter mouse resize mode, until mouse button is released."
|
||||
[handler ids shape]
|
||||
(letfn [(resize [shape initial layout objects [point lock? center? bounds-resize? point-snap]]
|
||||
(letfn [(resize [shape initial layout objects [point lock? center? point-snap]]
|
||||
(let [selrect (dm/get-prop shape :selrect)
|
||||
width (dm/get-prop selrect :width)
|
||||
height (dm/get-prop selrect :height)
|
||||
@@ -240,59 +235,7 @@
|
||||
(not (mth/close? (dm/get-prop scalev :x) 1))
|
||||
|
||||
change-height?
|
||||
(not (mth/close? (dm/get-prop scalev :y) 1))
|
||||
|
||||
;; Calculate independent image bounds resize transform
|
||||
sx (dm/get-prop scalev :x)
|
||||
sy (dm/get-prop scalev :y)
|
||||
w-new (* width sx)
|
||||
h-new (* height sy)
|
||||
|
||||
bounds-resize? (and ^boolean bounds-resize?
|
||||
(pos? w-new)
|
||||
(pos? h-new))
|
||||
|
||||
[dx dy] (if ^boolean center?
|
||||
[(/ (* width (- 1.0 sx)) 2.0)
|
||||
(/ (* height (- 1.0 sy)) 2.0)]
|
||||
[(case handler
|
||||
(:left :bottom-left :top-left) (* width (- 1.0 sx))
|
||||
0.0)
|
||||
(case handler
|
||||
(:top :top-left :top-right) (* height (- 1.0 sy))
|
||||
0.0)])
|
||||
|
||||
new-fills
|
||||
(when (and bounds-resize? (seq (:fills shape)))
|
||||
(mapv (fn [fill]
|
||||
(if-let [img-fill (:fill-image fill)]
|
||||
(let [tf (get img-fill :transform)
|
||||
nx0 (get tf :x 0.0)
|
||||
ny0 (get tf :y 0.0)
|
||||
nw0 (get tf :width 1.0)
|
||||
nh0 (get tf :height 1.0)
|
||||
nx' (/ (- (* nx0 width) dx) w-new)
|
||||
ny' (/ (- (* ny0 height) dy) h-new)
|
||||
nw' (/ nw0 sx)
|
||||
nh' (/ nh0 sy)]
|
||||
(assoc-in fill [:fill-image :transform]
|
||||
{:x nx' :y ny' :width nw' :height nh'}))
|
||||
fill))
|
||||
(:fills shape)))
|
||||
|
||||
new-fill-image
|
||||
(when (and bounds-resize? (some? (:fill-image shape)))
|
||||
(let [img-fill (:fill-image shape)
|
||||
tf (get img-fill :transform)
|
||||
nx0 (get tf :x 0.0)
|
||||
ny0 (get tf :y 0.0)
|
||||
nw0 (get tf :width 1.0)
|
||||
nh0 (get tf :height 1.0)
|
||||
nx' (/ (- (* nx0 width) dx) w-new)
|
||||
ny' (/ (- (* ny0 height) dy) h-new)
|
||||
nw' (/ nw0 sx)
|
||||
nh' (/ nh0 sy)]
|
||||
(assoc img-fill :transform {:x nx' :y ny' :width nw' :height nh'})))]
|
||||
(not (mth/close? (dm/get-prop scalev :y) 1))]
|
||||
|
||||
(cond-> (ctm/empty)
|
||||
(some? displacement)
|
||||
@@ -315,30 +258,18 @@
|
||||
(and new-grow-type (not= new-grow-type (dm/get-prop shape :grow-type)))
|
||||
(ctm/change-property :grow-type new-grow-type)
|
||||
|
||||
(and bounds-resize? (some? new-fills))
|
||||
(ctm/change-property :fills new-fills)
|
||||
|
||||
(and bounds-resize? (some? new-fill-image))
|
||||
(ctm/change-property :fill-image new-fill-image)
|
||||
|
||||
^boolean scale-text
|
||||
(ctm/scale-content (dm/get-prop scalev :x)))))
|
||||
|
||||
;; Unifies the instantaneous proportion lock modifier
|
||||
;; activated by Shift key and the shapes own proportion
|
||||
;; lock flag that can be activated on element options.
|
||||
(normalize-proportion-lock [[point shift? alt? mod?]]
|
||||
(let [has-img? (shape-has-image-fill? shape)
|
||||
bounds-resize? (and has-img? (boolean mod?))
|
||||
proportion-lock? (:proportion-lock shape)
|
||||
lock? (if bounds-resize?
|
||||
(boolean shift?)
|
||||
(or ^boolean proportion-lock?
|
||||
^boolean shift?))]
|
||||
(normalize-proportion-lock [[point shift? alt?]]
|
||||
(let [proportion-lock? (:proportion-lock shape)]
|
||||
[point
|
||||
lock?
|
||||
alt?
|
||||
bounds-resize?]))]
|
||||
(or ^boolean proportion-lock?
|
||||
^boolean shift?)
|
||||
alt?]))]
|
||||
(reify
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
@@ -366,10 +297,10 @@
|
||||
resize-events-stream
|
||||
(->> ms/mouse-position
|
||||
(rx/filter some?)
|
||||
(rx/with-latest-from ms/mouse-position-shift ms/mouse-position-alt ms/mouse-position-mod)
|
||||
(rx/with-latest-from ms/mouse-position-shift ms/mouse-position-alt)
|
||||
(rx/map normalize-proportion-lock)
|
||||
(rx/switch-map
|
||||
(fn [[point _ _ _ :as current]]
|
||||
(fn [[point _ _ :as current]]
|
||||
(->> (snap/closest-snap-point page-id shapes objects layout zoom focus point)
|
||||
(rx/map #(conj current %)))))
|
||||
(rx/map #(resize shape initial-position layout objects %))
|
||||
|
||||
@@ -764,7 +764,7 @@
|
||||
(remove #(= (:id %) component-id))
|
||||
(filter #(= (dm/get-in % [:variant-properties pos :value]) val))
|
||||
(reverse))
|
||||
nearest-comp (apply min-key #(ctv/distance target-props (:variant-properties %)) valid-comps)
|
||||
nearest-comp (apply min-key #(ctv/properties-distance target-props (:variant-properties %)) valid-comps)
|
||||
shape-parents (cfh/get-parents-with-self current-page-objects (:parent-id shape))
|
||||
nearest-comp-children (cfh/get-children-with-self component-page-objects (:main-instance-id nearest-comp))
|
||||
comps-nesting-loop? (seq? (cfh/components-nesting-loop? nearest-comp-children shape-parents))
|
||||
|
||||
@@ -119,43 +119,31 @@
|
||||
(if (:fill-image value)
|
||||
(let [uri (cf/resolve-file-media (:fill-image value))
|
||||
keep-ar? (-> value :fill-image :keep-aspect-ratio)
|
||||
tf (-> value :fill-image :transform)
|
||||
img-x (if (some? tf) (* (get tf :x 0) width) 0)
|
||||
img-y (if (some? tf) (* (get tf :y 0) height) 0)
|
||||
img-w (if (some? tf) (* (get tf :width 1) width) width)
|
||||
img-h (if (some? tf) (* (get tf :height 1) height) height)
|
||||
image-props #js {:id (dm/str "fill-image-" render-id "-" fill-index)
|
||||
:href (get embed uri uri)
|
||||
:preserveAspectRatio (if keep-ar? "xMidYMid slice" "none")
|
||||
:x img-x
|
||||
:y img-y
|
||||
:width img-w
|
||||
:height img-h
|
||||
:width width
|
||||
:height height
|
||||
:key (dm/str fill-index)
|
||||
:opacity (:fill-opacity value)}]
|
||||
[:> :image image-props])
|
||||
[:> :rect props])))
|
||||
|
||||
(when ^boolean has-image?
|
||||
(let [tf (-> image :transform)
|
||||
img-x (if (some? tf) (* (get tf :x 0) width) 0)
|
||||
img-y (if (some? tf) (* (get tf :y 0) height) 0)
|
||||
img-w (if (some? tf) (* (get tf :width 1) width) width)
|
||||
img-h (if (some? tf) (* (get tf :height 1) height) height)]
|
||||
[:g
|
||||
;; We add this shape to add a padding so the patter won't repeat
|
||||
;; Issue: https://tree.taiga.io/project/penpot/issue/5583
|
||||
[:rect {:x 0
|
||||
:y 0
|
||||
:width (* width no-repeat-padding)
|
||||
:height (* height no-repeat-padding)
|
||||
:fill "none"}]
|
||||
[:image {:href uri
|
||||
:preserveAspectRatio "none"
|
||||
:x img-x
|
||||
:y img-y
|
||||
:width img-w
|
||||
:height img-h}]]))]])])))
|
||||
[:g
|
||||
;; We add this shape to add a padding so the patter won't repeat
|
||||
;; Issue: https://tree.taiga.io/project/penpot/issue/5583
|
||||
[:rect {:x 0
|
||||
:y 0
|
||||
:width (* width no-repeat-padding)
|
||||
:height (* height no-repeat-padding)
|
||||
:fill "none"}]
|
||||
[:image {:href uri
|
||||
:preserveAspectRatio "none"
|
||||
:x 0
|
||||
:y 0
|
||||
:width width
|
||||
:height height}]])]])])))
|
||||
|
||||
(mf/defc fills
|
||||
{::mf/wrap-props false}
|
||||
|
||||
@@ -95,16 +95,6 @@
|
||||
[:span {:class (stl/css :icon)}
|
||||
deprecated-icon/tick])]
|
||||
|
||||
[:li {:class (stl/css-case
|
||||
:dropdown-element true
|
||||
:selected (= :mentions cmode))
|
||||
:data-value "mentions"
|
||||
:on-click update-mode}
|
||||
[:span {:class (stl/css :label)} (tr "labels.show-mentions")]
|
||||
(when (= :mentions cmode)
|
||||
[:span {:class (stl/css :icon)}
|
||||
deprecated-icon/tick])]
|
||||
|
||||
[:li {:class (stl/css :separator)}]
|
||||
|
||||
[:li {:class (stl/css-case
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
//
|
||||
// Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
@use "ds/_borders.scss" as *;
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
|
||||
// COMMENT DROPDOWN ON HEADER
|
||||
@@ -93,12 +92,7 @@
|
||||
}
|
||||
|
||||
.separator {
|
||||
position: relative;
|
||||
block-size: var(--sp-xs);
|
||||
inline-size: calc(100% + var(--sp-s));
|
||||
border-top: $b-1 solid var(--color-background-quaternary);
|
||||
left: calc(-1 * var(--sp-xs));
|
||||
margin-top: var(--sp-s);
|
||||
height: deprecated.$s-8;
|
||||
}
|
||||
|
||||
// FLOATING COMMENT
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
// Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/_borders.scss" as *;
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
|
||||
.comments-section {
|
||||
@@ -113,12 +112,7 @@
|
||||
}
|
||||
|
||||
.separator {
|
||||
position: relative;
|
||||
block-size: var(--sp-xs);
|
||||
inline-size: calc(100% + var(--sp-s));
|
||||
border-top: $b-1 solid var(--color-background-quaternary);
|
||||
left: calc(-1 * var(--sp-xs));
|
||||
margin-top: var(--sp-s);
|
||||
height: deprecated.$s-12;
|
||||
}
|
||||
|
||||
.comments-section-content {
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
graphics 0
|
||||
typographies (count (:typographies data))
|
||||
components (count (->> (ctkl/components-seq data)
|
||||
(remove #(cfv/is-secondary-variant? % data))))
|
||||
(remove #(cfv/is-secondary-variant? data %))))
|
||||
empty? (and (zero? components)
|
||||
(zero? graphics)
|
||||
(zero? colors)
|
||||
|
||||
@@ -327,7 +327,7 @@
|
||||
(mf/with-memo [filters library]
|
||||
(as-> (into [] (ctkl/components-seq library)) $
|
||||
(cmm/apply-filters $ filters)
|
||||
(remove #(cfv/is-secondary-variant? % library) $)))
|
||||
(remove #(cfv/is-secondary-variant? library %) $)))
|
||||
|
||||
filtered-typographies
|
||||
(mf/with-memo [filters typographies]
|
||||
|
||||
@@ -710,7 +710,7 @@
|
||||
:components
|
||||
vals
|
||||
(remove #(true? (:deleted %)))
|
||||
(remove #(cfv/is-secondary-variant? % current-lib-data))
|
||||
(remove #(cfv/is-secondary-variant? current-lib-data %))
|
||||
(map #(assoc % :full-name (cpn/merge-path-item-with-dot (:path %) (:name %)))))
|
||||
|
||||
count-variants (fn [component]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
;; 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 SUBSIDIARY SL
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns app.main.ui.workspace.viewport.path-state
|
||||
(:require
|
||||
|
||||
@@ -1033,7 +1033,7 @@
|
||||
components (->> data
|
||||
:components
|
||||
(remove (comp :deleted second))
|
||||
(remove (comp #(cfv/is-secondary-variant? % data) second))
|
||||
(remove (comp #(cfv/is-secondary-variant? data %) second))
|
||||
(map first)
|
||||
(map #(lib-component-proxy plugin-id file-id %)))]
|
||||
(apply array components)))}
|
||||
|
||||
@@ -41,9 +41,7 @@
|
||||
:m1 :margin-top
|
||||
:m2 :margin-right
|
||||
:m3 :margin-bottom
|
||||
:m4 :margin-left
|
||||
|
||||
:font-family :font-families})
|
||||
:m4 :margin-left})
|
||||
|
||||
(def ^:private map:token-attr-plugin->token-attr
|
||||
(merge
|
||||
|
||||
@@ -76,48 +76,3 @@
|
||||
(t/is (= (:stroke-alignment stroke') :inner))
|
||||
(t/is (= (:stroke-color stroke') "#FABADA"))
|
||||
(t/is (= (:stroke-width stroke') 2))))))))
|
||||
(t/deftest test-update-stroke-color-preserves-dash-gap
|
||||
;; Custom dash/gap on a dashed stroke describe stroke geometry, not color;
|
||||
;; a stroke color change must preserve them (issue #11549).
|
||||
(t/async
|
||||
done
|
||||
(let [store (ths/setup-store
|
||||
(-> (cthf/sample-file :file1 :page-label :page1)
|
||||
(cths/add-sample-shape :shape1 :strokes
|
||||
[{:stroke-color "#000000"
|
||||
:stroke-opacity 1
|
||||
:stroke-width 2
|
||||
:stroke-style :dashed
|
||||
:stroke-dash 4
|
||||
:stroke-gap 20}])
|
||||
(cths/add-sample-shape :shape2 :strokes
|
||||
[{:stroke-color "#000000"
|
||||
:stroke-opacity 1
|
||||
:stroke-width 2
|
||||
:stroke-style :dashed}])))
|
||||
events [(dc/change-stroke-color #{(cthi/id :shape1)} {:color "#FABADA"} 0)
|
||||
(dc/change-stroke-color #{(cthi/id :shape2)} {:color "#FABADA"} 0)]]
|
||||
(ths/run-store
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [objects (dsh/lookup-page-objects new-state)
|
||||
shape1' (get objects (cthi/id :shape1))
|
||||
stroke1' (first (:strokes shape1'))
|
||||
shape2' (get objects (cthi/id :shape2))
|
||||
stroke2' (first (:strokes shape2'))]
|
||||
|
||||
;; dashed stroke with custom dash/gap keeps them after color change
|
||||
(t/is (some? shape1'))
|
||||
(t/is (= (:stroke-color stroke1') "#FABADA"))
|
||||
(t/is (= (:stroke-style stroke1') :dashed))
|
||||
(t/is (= (:stroke-width stroke1') 2))
|
||||
(t/is (= (:stroke-dash stroke1') 4))
|
||||
(t/is (= (:stroke-gap stroke1') 20))
|
||||
|
||||
;; dashed stroke without explicit dash/gap stays unset:
|
||||
;; no implicit default is materialized into stored data
|
||||
(t/is (some? shape2'))
|
||||
(t/is (= (:stroke-color stroke2') "#FABADA"))
|
||||
(t/is (= (:stroke-style stroke2') :dashed))
|
||||
(t/is (nil? (:stroke-dash stroke2')))
|
||||
(t/is (nil? (:stroke-gap stroke2')))))))))
|
||||
@@ -1,56 +0,0 @@
|
||||
;; 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 Sucursal en España SL
|
||||
|
||||
(ns frontend-tests.data.comments-filters-test
|
||||
(:require
|
||||
[app.main.data.comments :as dcmt]
|
||||
[app.util.storage :as storage]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(def ^:private storage-key
|
||||
:app.main.data.comments/hide-resolved-comments?)
|
||||
|
||||
(t/deftest test-merge-persisted-filters-default
|
||||
(let [prev (get @storage/user storage-key)]
|
||||
(try
|
||||
(swap! storage/user dissoc storage-key)
|
||||
(t/is (= {:show :all} (dcmt/merge-persisted-filters nil)))
|
||||
(t/is (= {:show :all} (dcmt/merge-persisted-filters {})))
|
||||
(finally
|
||||
(if (some? prev)
|
||||
(swap! storage/user assoc storage-key prev)
|
||||
(swap! storage/user dissoc storage-key))))))
|
||||
|
||||
(t/deftest test-merge-persisted-filters-hide-resolved
|
||||
(let [prev (get @storage/user storage-key)]
|
||||
(try
|
||||
(swap! storage/user assoc storage-key true)
|
||||
(t/is (= {:show :pending} (dcmt/merge-persisted-filters nil)))
|
||||
(finally
|
||||
(if (some? prev)
|
||||
(swap! storage/user assoc storage-key prev)
|
||||
(swap! storage/user dissoc storage-key))))))
|
||||
|
||||
(t/deftest test-merge-persisted-filters-keeps-session-value
|
||||
(let [prev (get @storage/user storage-key)]
|
||||
(try
|
||||
(swap! storage/user assoc storage-key true)
|
||||
(t/is (= {:show :all :mode :yours}
|
||||
(dcmt/merge-persisted-filters {:show :all :mode :yours})))
|
||||
(finally
|
||||
(if (some? prev)
|
||||
(swap! storage/user assoc storage-key prev)
|
||||
(swap! storage/user dissoc storage-key))))))
|
||||
|
||||
(t/deftest test-update-filters-updates-show
|
||||
(let [event (dcmt/update-filters {:show :pending})
|
||||
state (ptk/update event {})]
|
||||
(t/is (= :pending (get-in state [:comments-local :show])))
|
||||
|
||||
(let [event (dcmt/update-filters {:show :all})
|
||||
state (ptk/update event state)]
|
||||
(t/is (= :all (get-in state [:comments-local :show]))))))
|
||||