Compare commits

...

4 Commits

Author SHA1 Message Date
Andrey Antukh
db00456277 💄 Add format rule for code comments 2026-01-27 13:37:28 +01:00
Eva Marco
2523096fdd 🐛 Fix css rule (#8206) 2026-01-27 12:30:14 +01:00
Xaviju
8e63c4e3e8 ♻️ Review remap interface and interaction (#8168)
* ♻️ Review remap interface and interaction
* ♻️ Fix remapping feature tests
2026-01-27 11:18:34 +01:00
Pablo Alba
d5abc52dac 🎉 Add first integration with nitrate (#7803)
* 🐛 Display missing selected tokens set info (#8098)

* 🐛 Display missing selected tokens set info

*  Add integration tests to verify current active set

* 🎉 Integration with nitrate platform

* 🐛 Fix nitrate get-teams returns deleted teams

*  Add nitrate to tmux devenv

*  Add retry and validation to nitrate module

*  Add photoUrl to profile on nitrate authenticate

*  Move nitrate url to an env variable

* ♻️ Change Nitrate organization-id schema to text

* ♻️ Cleanup unused imports

* 🔧 Add control-center to nginx

*  Add create org link

* 🔧 Fix nginx entrypoint

* 🐛 Fix control-center proxy pass

* 🎉 Add nitrate licence check

* Revert " Add nitrate to tmux devenv"

This reverts commit dc6f6c4589.

*  Add feature flag check

* 🐛 Rename licences for licenses

*  MR changes

*  MR changes 2

* 📎 Add the ability to have local config on start backend

* 📎 Add FIXME comment

---------

Co-authored-by: Xaviju <xavier.julian@kaleidos.net>
Co-authored-by: Juanfran <juanfran.ag@gmail.com>
Co-authored-by: Yamila Moreno <yamila.moreno@kaleidos.net>
Co-authored-by: Marina López <marina.lopez.yap@gmail.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-01-27 10:04:53 +01:00
98 changed files with 1354 additions and 845 deletions

View File

@@ -2,6 +2,8 @@
:remove-multiple-non-indenting-spaces? false
:remove-surrounding-whitespace? true
:remove-consecutive-blank-lines? false
:indent-line-comments? true
:parallel? true
:extra-indents {rumext.v2/fnc [[:inner 0]]
cljs.test/async [[:inner 0]]
promesa.exec/thread [[:inner 0]]

3
.gitignore vendored
View File

@@ -21,6 +21,7 @@
.rebel_readline_history
.repl
.shadow-cljs
.pnpm-store/
/*.jpg
/*.md
/*.png
@@ -44,6 +45,7 @@
/backend/resources/public/media
/backend/target/
/backend/experiments
/backend/scripts/_env.local
/bundle*
/cd.md
/clj-profiler/
@@ -74,6 +76,7 @@
/library/target/
/library/*.zip
/external
/penpot-nitrate
clj-profiler/
node_modules

View File

@@ -13,6 +13,7 @@ export PENPOT_FLAGS="\
disable-login-with-google \
disable-login-with-github \
disable-login-with-gitlab \
disable-telemetry \
enable-backend-worker \
enable-backend-asserts \
disable-feature-fdata-pointer-map \
@@ -55,6 +56,8 @@ export PENPOT_OBJECTS_STORAGE_BACKEND=s3
export PENPOT_OBJECTS_STORAGE_S3_ENDPOINT=http://minio:9000
export PENPOT_OBJECTS_STORAGE_S3_BUCKET=penpot
export PENPOT_NITRATE_BACKEND_URI=http://localhost:3000/control-center
export JAVA_OPTS="\
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
-Djdk.attach.allowAttachSelf \

View File

@@ -3,6 +3,10 @@
SCRIPT_DIR=$(dirname $0);
source $SCRIPT_DIR/_env;
if [ -f $SCRIPT_DIR/_env.local ]; then
source $SCRIPT_DIR/_env.local;
fi
# Initialize MINIO config
setup_minio;

View File

@@ -3,6 +3,11 @@
SCRIPT_DIR=$(dirname $0);
source $SCRIPT_DIR/_env;
if [ -f $SCRIPT_DIR/_env.local ]; then
source $SCRIPT_DIR/_env.local;
fi
export OPTIONS="-A:dev"
entrypoint=${1:-app.main};

View File

@@ -3,6 +3,10 @@
SCRIPT_DIR=$(dirname $0);
source $SCRIPT_DIR/_env;
if [ -f $SCRIPT_DIR/_env.local ]; then
source $SCRIPT_DIR/_env.local;
fi
# Initialize MINIO config
setup_minio;

View File

@@ -225,6 +225,8 @@
[:netty-io-threads {:optional true} ::sm/int]
[:executor-threads {:optional true} ::sm/int]
[:nitrate-backend-uri {:optional true} ::sm/uri]
;; DEPRECATED
[:assets-storage-backend {:optional true} :keyword]
[:storage-assets-fs-directory {:optional true} :string]

View File

@@ -323,6 +323,7 @@
{::http.client/client (ig/ref ::http.client/client)
::db/pool (ig/ref ::db/pool)
::rds/pool (ig/ref ::rds/pool)
:app.nitrate/client (ig/ref :app.nitrate/client)
::wrk/executor (ig/ref ::wrk/netty-executor)
::session/manager (ig/ref ::session/manager)
::ldap/provider (ig/ref ::ldap/provider)
@@ -339,6 +340,9 @@
::email/blacklist (ig/ref ::email/blacklist)
::email/whitelist (ig/ref ::email/whitelist)}
:app.nitrate/client
{::http.client/client (ig/ref ::http.client/client)}
:app.rpc/management-methods
{::http.client/client (ig/ref ::http.client/client)
::db/pool (ig/ref ::db/pool)
@@ -348,6 +352,7 @@
::sto/storage (ig/ref ::sto/storage)
::mtx/metrics (ig/ref ::mtx/metrics)
::mbus/msgbus (ig/ref ::mbus/msgbus)
:app.nitrate/client (ig/ref :app.nitrate/client)
::rds/client (ig/ref ::rds/client)
::setup/props (ig/ref ::setup/props)}

147
backend/src/app/nitrate.clj Normal file
View File

@@ -0,0 +1,147 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.nitrate
"Module that make calls to the external nitrate aplication"
(:require
[app.common.logging :as l]
[app.common.schema :as sm]
[app.config :as cf]
[app.http.client :as http]
[app.rpc :as-alias rpc]
[app.setup :as-alias setup]
[app.util.json :as json]
[clojure.core :as c]
[integrant.core :as ig]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- request-builder
[cfg method uri management-key profile-id]
(fn []
(http/req! cfg {:method method
:headers {"content-type" "application/json"
"accept" "application/json"
"x-shared-key" management-key
"x-profile-id" (str profile-id)}
:uri uri
:version :http1.1})))
(defn- with-retries
[handler max-retries]
(fn []
(loop [attempt 1]
(let [result (try
(handler)
(catch Exception e
(if (< attempt max-retries)
::retry
(do
;; TODO Error handling
(l/error :hint "request fail after multiple retries" :cause e)
nil))))]
(if (= result ::retry)
(recur (inc attempt))
result)))))
(defn- with-validate [handler uri schema]
(fn []
(let [coercer-http (sm/coercer schema
:type :validation
:hint (str "invalid data received calling " uri))]
(try
(coercer-http (-> (handler) :body json/decode))
(catch Exception e
;; TODO Error handling
(l/error :hint "error validating json response" :cause e)
nil)))))
(defn- request-to-nitrate
[{:keys [::management-key] :as cfg} method uri schema {:keys [::rpc/profile-id] :as params}]
(let [full-http-call (-> (request-builder cfg method uri management-key profile-id)
(with-retries 3)
(with-validate uri schema))]
(full-http-call)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; API
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn call
[cfg method params]
(when (contains? cf/flags :nitrate)
(let [client (get cfg ::client)
method (get client method)]
(method params))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:private schema:organization
[:map
[:id ::sm/text]
[:name ::sm/text]])
(def ^:private schema:user
[:map
[:valid ::sm/boolean]])
(defn- get-team-org
[cfg {:keys [team-id] :as params}]
(let [baseuri (cf/get :nitrate-backend-uri)]
(request-to-nitrate cfg :get (str baseuri "/api/teams/" (str team-id)) schema:organization params)))
(defn- is-valid-user
[cfg {:keys [profile-id] :as params}]
(let [baseuri (cf/get :nitrate-backend-uri)]
(request-to-nitrate cfg :get (str baseuri "/api/users/" (str profile-id)) schema:user params)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INITIALIZATION
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod ig/init-key ::client
[_ {:keys [::setup/props] :as cfg}]
(if (contains? cf/flags :nitrate)
(let [management-key (or (cf/get :management-api-key)
(get props :management-key))
cfg (assoc cfg ::management-key management-key)]
{:get-team-org (partial get-team-org cfg)
:is-valid-user (partial is-valid-user cfg)})
{}))
(defmethod ig/halt-key! ::client
[_ {:keys []}]
(do :stuff))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; UTILS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn add-nitrate-licence-to-profile
[cfg profile]
(try
(let [nitrate-licence (call cfg :is-valid-user {:profile-id (:id profile)})]
(assoc profile :nitrate-licence (:valid nitrate-licence)))
(catch Throwable cause
(l/error :hint "failed to get nitrate licence"
:profile-id (:id profile)
:cause cause)
profile)))
(defn add-org-to-team
[cfg team params]
(let [params (assoc (or params {}) :team-id (:id team))
org (call cfg :get-team-org params)]
(assoc team :organization-id (:id org) :organization-name (:name org))))

View File

@@ -301,6 +301,7 @@
(let [cfg (assoc cfg ::type "management" ::metrics-id :rpc-management-timing)]
(->> (sv/scan-ns
'app.rpc.management.subscription
'app.rpc.management.nitrate
'app.rpc.management.exporter)
(map (partial process-method cfg "management" wrap-management))
(into {}))))

View File

@@ -21,6 +21,7 @@
[app.loggers.audit :as audit]
[app.main :as-alias main]
[app.media :as media]
[app.nitrate :as nitrate]
[app.rpc :as-alias rpc]
[app.rpc.climit :as climit]
[app.rpc.doc :as-alias doc]
@@ -88,6 +89,8 @@
;; --- QUERY: Get profile (own)
(sv/defmethod ::get-profile
{::rpc/auth false
::doc/added "1.18"
@@ -98,9 +101,13 @@
;; no profile-id is in session, and when db call raises not found. In all other
;; cases we need to reraise the exception.
(try
(-> (get-profile pool profile-id)
(strip-private-attrs)
(update :props filter-props))
(let [profile (-> (get-profile pool profile-id)
(strip-private-attrs)
(update :props filter-props))]
(if (contains? cf/flags :nitrate)
(nitrate/add-nitrate-licence-to-profile cfg profile)
profile))
(catch Throwable _
{:id uuid/zero :fullname "Anonymous User"})))

View File

@@ -23,6 +23,7 @@
[app.main :as-alias main]
[app.media :as media]
[app.msgbus :as mbus]
[app.nitrate :as nitrate]
[app.rpc :as-alias rpc]
[app.rpc.commands.profile :as profile]
[app.rpc.doc :as-alias doc]
@@ -190,7 +191,9 @@
::sm/params schema:get-teams}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
(dm/with-open [conn (db/open pool)]
(get-teams conn profile-id)))
(cond->> (get-teams conn profile-id)
(contains? cf/flags :nitrate)
(map #(nitrate/add-org-to-team cfg % params)))))
(def ^:private sql:get-owned-teams
"SELECT t.id, t.name,

View File

@@ -248,11 +248,11 @@
invitations (into #{}
(comp
;; We don't re-send invitations to
;; already existing members
;; We don't re-send invitations to
;; already existing members
(remove #(contains? team-members (:email %)))
;; We don't send invitations to
;; join-requested members
;; We don't send invitations to
;; join-requested members
(remove #(contains? join-requests (:email %)))
(map (fn [{:keys [email role]}]
(create-invitation cfg

View File

@@ -0,0 +1,112 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.rpc.management.nitrate
"Internal Nitrate HTTP API.
Provides authenticated access to organization management and token validation endpoints.
All requests must include a valid shared key token in the `x-shared-key` header, and
a cookie `auth-token` with the user token.
They will return `401 Unauthorized` if the shared key or user token are invalid."
(:require
[app.common.schema :as sm]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.msgbus :as mbus]
[app.rpc :as-alias rpc]
[app.rpc.commands.files :as files]
[app.rpc.commands.profile :as profile]
[app.rpc.doc :as doc]
[app.util.services :as sv]))
;; ---- API: authenticate
(def ^:private schema:profile
[:map
[:id ::sm/uuid]
[:name :string]
[:email :string]
[:photo-url :string]])
(sv/defmethod ::authenticate
"Authenticate an user
@api GET /authenticate
@returns
200 OK: Returns the authenticated user."
{::doc/added "2.12"
::sm/result schema:profile}
[cfg {:keys [::rpc/profile-id] :as params}]
(let [profile (profile/get-profile cfg profile-id)]
{:id (get profile :id)
:name (get profile :fullname)
:email (get profile :email)
:photo-url (files/resolve-public-uri (get profile :photo-id))}))
;; ---- API: get-teams
(def ^:private sql:get-teams
"SELECT t.*
FROM team AS t
JOIN team_profile_rel AS tpr ON t.id = tpr.team_id
WHERE tpr.profile_id = ?
AND tpr.is_owner = 't'
AND t.is_default = 'f'
AND t.deleted_at is null;")
(def ^:private schema:team
[:map
[:id ::sm/uuid]
[:name :string]])
(def ^:private schema:get-teams-result
[:vector schema:team])
(sv/defmethod ::get-teams
"List teams for which current user is owner.
@api GET /get-teams
@returns
200 OK: Returns the list of teams for the user."
{::doc/added "2.12"
::sm/result schema:get-teams-result}
[cfg {:keys [::rpc/profile-id]}]
(when (contains? cf/flags :nitrate)
(let [current-user-id (-> (profile/get-profile cfg profile-id) :id)]
(->> (db/exec! cfg [sql:get-teams current-user-id])
(map #(select-keys % [:id :name]))))))
;; ---- API: notify-team-change
(def ^:private schema:notify-team-change
[:map
[:id ::sm/uuid]
[:organization-id ::sm/text]])
(sv/defmethod ::notify-team-change
"Notify to Penpot a team change from nitrate
@api POST /notify-team-change
@returns
200 OK"
{::doc/added "2.12"
::sm/params schema:notify-team-change
::rpc/auth false}
[cfg {:keys [id organization-id organization-name]}]
(when (contains? cf/flags :nitrate)
(let [msgbus (::mbus/msgbus cfg)]
(mbus/pub! msgbus
;;TODO There is a bug on dashboard with teams notifications.
;;For now we send it to uuid/zero instead of team-id
:topic uuid/zero
:message {:type :team-org-change
:team-id id
:organization-id organization-id
:organization-name organization-name}))))

View File

@@ -841,7 +841,7 @@
out (th/command! data)
error (:error out)]
;; (th/print-result! out)
;; (th/print-result! out)
(t/is (th/ex-info? error))
(t/is (th/ex-of-type? error :not-found))))
@@ -863,7 +863,7 @@
out (th/command! data)
error (:error out)]
;; (th/print-result! out)
;; (th/print-result! out)
(t/is (th/ex-info? error))
(t/is (th/ex-of-type? error :not-found))))
@@ -1261,7 +1261,7 @@
(t/is (= 1 (count rows)))
(t/is (every? #(some? (:data %)) rows)))
;; Mark the file ellegible again for GC
;; Mark the file ellegible again for GC
(th/db-update! :file
{:has-media-trimmed false}
{:id (:id file)})
@@ -1318,7 +1318,7 @@
{:file-id (:id file)
:type "fragment"}
{:order-by [:created-at]})]
;; (pp/pprint rows)
;; (pp/pprint rows)
(t/is (= 2 (count rows)))
(t/is (nil? (:data row1)))
(t/is (= "storage" (:backend row1)))

View File

@@ -536,7 +536,7 @@
:token rtoken}
{:keys [result error] :as out} (th/command! data)]
;; (th/print-result! out)
;; (th/print-result! out)
(t/is (nil? error))
(t/is (map? result))
(t/is (string? (:invitation-token result))))))

View File

@@ -30,7 +30,7 @@
:team-id (:id team)
:name "test project"}
out (th/command! data)]
;; (th/print-result! out)
;; (th/print-result! out)
(t/is (nil? (:error out)))
(let [result (:result out)]
@@ -93,7 +93,7 @@
:id project-id}
out (th/command! data)]
;; (th/print-result! out)
;; (th/print-result! out)
(t/is (nil? (:error out)))
(t/is (nil? (:result out))))

View File

@@ -1092,9 +1092,9 @@
(if (number? num)
(try
(let [num-str (mth/to-fixed num precision)
;; Remove all trailing zeros after the comma 100.00000
;; Remove all trailing zeros after the comma 100.00000
num-str (str/replace num-str trail-zeros-regex-1 "")]
;; Remove trailing zeros after a decimal number: 0.001|00|
;; Remove trailing zeros after a decimal number: 0.001|00|
(if-let [m (re-find trail-zeros-regex-2 num-str)]
(str/replace num-str (first m) (second m))
num-str))

View File

@@ -44,7 +44,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Set parent to root frame.
;; Set parent to root frame.
(log/debug :hint " -> set to " :parent-id uuid/zero)
(assoc shape :parent-id uuid/zero))]
@@ -57,7 +57,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [parent-shape]
; Add shape to parent's children list
;; Add shape to parent's children list
(log/debug :hint " -> add children to" :parent-id (:id parent-shape))
(update parent-shape :shapes conj (:id shape)))]
@@ -70,7 +70,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Remove duplicated
;; Remove duplicated
(log/debug :hint " -> remove duplicated children")
(update shape :shapes distinct))]
@@ -102,7 +102,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Locate the first frame in parents and set frame-id to it.
;; Locate the first frame in parents and set frame-id to it.
(let [page (ctpl/get-page file-data page-id)
frame (cfh/get-frame (:objects page) (:parent-id shape))
frame-id (or (:id frame) uuid/zero)]
@@ -118,7 +118,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Locate the first frame in parents and set frame-id to it.
;; Locate the first frame in parents and set frame-id to it.
(let [page (ctpl/get-page file-data page-id)
frame (cfh/get-frame (:objects page) (:parent-id shape))
frame-id (or (:id frame) uuid/zero)]
@@ -134,7 +134,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Set the :shape as main instance root
;; Set the :shape as main instance root
(log/debug :hint " -> set :main-instance")
(assoc shape :main-instance true))]
@@ -147,12 +147,13 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Set :component-file to local file
;; Set :component-file to local file
(log/debug :hint " -> set :component-file to local file")
(assoc shape :component-file (:id file-data)))]
; There is no solution that may recover it with confidence
;; (log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
;; shape)]
;; There is no solution that may recover it with confidence
;; (log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
;; shape)]
(log/dbg :hint "repairing shape :component-main-external" :id (:id shape) :name (:name shape) :page-id page-id)
(-> (pcb/empty-changes nil page-id)
@@ -166,12 +167,12 @@
repair-shape
(fn [shape]
; Detach the shape and convert it to non instance.
;; Detach the shape and convert it to non instance.
(log/debug :hint " -> detach shape" :shape-id (:id shape))
(ctk/detach-shape shape))]
; There is no solution that may recover it with confidence
;; (log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
;; shape)]
;; There is no solution that may recover it with confidence
;; (log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
;; shape)]
(log/dbg :hint "repairing shape :component-not-found" :id (:id shape) :name (:name shape) :page-id page-id)
(-> (pcb/empty-changes nil page-id)
@@ -184,7 +185,7 @@
repair-component
(fn [component]
; Assign main instance in the component to current shape
;; Assign main instance in the component to current shape
(log/debug :hint " -> assign main-instance-id" :component-id (:id component))
(assoc component :main-instance-id (:id shape)))
@@ -207,7 +208,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-component
(fn [component]
; Assign main instance in the component to current shape
;; Assign main instance in the component to current shape
(log/debug :hint " -> assign main-instance-page" :component-id (:id component))
(assoc component :main-instance-page page-id))]
(log/dbg :hint "repairing shape :invalid-main-instance-page" :id (:id shape) :name (:name shape) :page-id page-id)
@@ -219,7 +220,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; There is no solution that may recover it with confidence
;; There is no solution that may recover it with confidence
(log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
shape)]
@@ -232,7 +233,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Unset the :shape as main instance root
;; Unset the :shape as main instance root
(log/debug :hint " -> unset :main-instance")
(dissoc shape :main-instance))]
@@ -245,7 +246,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Convert the shape in a top copy root.
;; Convert the shape in a top copy root.
(log/debug :hint " -> set :component-root")
(assoc shape :component-root true))]
@@ -258,7 +259,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Convert the shape in a nested copy root.
;; Convert the shape in a nested copy root.
(log/debug :hint " -> unset :component-root")
(dissoc shape :component-root))]
@@ -307,8 +308,8 @@
(log/debug :hint " -> detach shape" :shape-id (:id shape))
(ctk/detach-shape shape))]
; If the shape still refers to the remote component, try to find the corresponding near one
; and link to it. If not, detach the shape.
;; If the shape still refers to the remote component, try to find the corresponding near one
;; and link to it. If not, detach the shape.
(log/dbg :hint "repairing shape :ref-shape-not-found" :id (:id shape) :name (:name shape) :page-id page-id)
(if (some? matching-shape)
(-> (pcb/empty-changes nil page-id)
@@ -324,7 +325,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Convert shape in a normal copy, removing nested copy status
;; Convert shape in a normal copy, removing nested copy status
(log/debug :hint " -> unhead shape")
(ctk/unhead-shape shape))]
@@ -337,7 +338,7 @@
[_ {:keys [shape page-id args] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Convert shape in a nested head, adding component info
;; Convert shape in a nested head, adding component info
(log/debug :hint " -> reroot shape")
(ctk/rehead-shape shape (:component-file args) (:component-id args)))]
@@ -350,8 +351,9 @@
[_ {:keys [shape args] :as error} file-data _]
(let [repair-component
(fn [component]
(let [objects (:objects component) ;; we only have encounter this on deleted components,
;; so the relevant objects are inside the component
(let [objects (:objects component)
;; we only have encounter this on deleted components,
;; so the relevant objects are inside the component
to-detach (->> (:cycles-ids args)
(map #(get objects %))
(map #(ctn/get-head-shape objects %))
@@ -378,7 +380,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Remove shape-ref
;; Remove shape-ref
(log/debug :hint " -> unset :shape-ref")
(dissoc shape :shape-ref))]
@@ -391,7 +393,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Convert the shape in a nested main head.
;; Convert the shape in a nested main head.
(log/debug :hint " -> unset :component-root")
(dissoc shape :component-root))]
@@ -404,7 +406,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Convert the shape in a top main head.
;; Convert the shape in a top main head.
(log/debug :hint " -> set :component-root")
(assoc shape :component-root true))]
@@ -418,7 +420,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Convert the shape in a nested copy head.
;; Convert the shape in a nested copy head.
(log/debug :hint " -> unset :component-root")
(dissoc shape :component-root))]
@@ -431,7 +433,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Convert the shape in a top copy root.
;; Convert the shape in a top copy root.
(log/debug :hint " -> set :component-root")
(assoc shape :component-root true))]
@@ -444,7 +446,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Detach the shape and convert it to non instance.
;; Detach the shape and convert it to non instance.
(log/debug :hint " -> detach shape" :shape-id (:id shape))
(ctk/detach-shape shape))]
@@ -457,7 +459,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Detach the shape and convert it to non instance.
;; Detach the shape and convert it to non instance.
(log/debug :hint " -> detach shape" :shape-id (:id shape))
(ctk/detach-shape shape))]
@@ -470,7 +472,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; There is no solution that may recover it with confidence
;; There is no solution that may recover it with confidence
(log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
shape)]
@@ -483,7 +485,7 @@
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
(fn [shape]
; Convert the shape in a frame.
;; Convert the shape in a frame.
(log/debug :hint " -> set :type :frame")
(assoc shape :type :frame
:fills []
@@ -502,7 +504,7 @@
[_ {:keys [shape] :as error} file-data _]
(let [repair-component
(fn [component]
; Remove the objects key, or set it to {} if the component is deleted
;; Remove the objects key, or set it to {} if the component is deleted
(if (:deleted component)
(do
(log/debug :hint " -> set :objects {}")

View File

@@ -430,8 +430,8 @@
(assoc :frame-id frame-id)
(assoc :svg-viewbox vbox)
(assoc :svg-attrs props)
;; We need to ensure fills are empty on import process
;; because setup-shape assings one by default.
;; We need to ensure fills are empty on import process
;; because setup-shape assings one by default.
(assoc :fills [])
(merge radius-attrs)))))

View File

@@ -148,7 +148,10 @@
;; A temporal flag, enables backend code use more extensivelly
;; redis for caching data
:redis-cache})
:redis-cache
;; Activates the nitrate module
:nitrate})
(def all-flags
(set/union email login varia))

View File

@@ -49,9 +49,9 @@
(def log-container-ids #{})
(def updatable-attrs (->> (seq (keys ctk/sync-attrs))
;; We don't update the flex-child attrs
;; We don't update the flex-child attrs
(remove ctk/swap-keep-attrs)
;; We don't do automatic update of the `layout-grid-cells` property.
;; We don't do automatic update of the `layout-grid-cells` property.
(remove #(= :layout-grid-cells %))))
(defn enabled-shape?
@@ -1898,10 +1898,10 @@
(gsh/absolute-move shape new-pos)))
(defn- switch-path-change-value
[prev-shape ;; The shape before the switch
current-shape ;; The shape after the switch (a clean copy)
ref-shape ;; The referenced shape on the main component
;; before the switch
[prev-shape ; The shape before the switch
current-shape ; The shape after the switch (a clean copy)
ref-shape ; The referenced shape on the main component
; before the switch
attr]
(let [old-width (-> ref-shape :selrect :width)
new-width (-> prev-shape :selrect :width)
@@ -1918,10 +1918,9 @@
(defn- switch-text-change-value
[prev-content ;; The :content of the text before the switch
current-content ;; The :content of the text after the switch (a clean copy)
ref-content touched] ;; The :content of the referenced text on the main component
;; before the switch
[prev-content ; The :content of the text before the switch
current-content ; The :content of the text after the switch (a clean copy)
ref-content touched] ; The :content of the referenced text on the main component before the switch
(let [;; We need the differences between the contents on the main
;; components. current-content is the content of a clean copy,
;; so for all effects its the same as the content on its main
@@ -2845,8 +2844,8 @@
duplicating-component?
true
(and remove-swap-slot?
;; only remove swap slot of children when the current shape
;; is not a subinstance head nor a instance root
;; only remove swap slot of children when the current shape
;; is not a subinstance head nor a instance root
(not subinstance-head?)
(not instance-root?))
variant-props))
@@ -2902,7 +2901,7 @@
variant-props)
changes))
;; We need to check the changes to get the ids-map
;; We need to check the changes to get the ids-map
ids-map
(into {}
(comp

View File

@@ -138,12 +138,12 @@
ids (cfh/clean-loops objects ids)
in-component-copy?
(fn [shape-id]
;; Look for shapes that are inside a component copy, but are
;; not the root. In this case, they must not be deleted,
;; but hidden (to be able to recover them more easily).
;; 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
;; Look for shapes that are inside a component copy, but are
;; not the root. In this case, they must not be deleted,
;; but hidden (to be able to recover them more easily).
;; 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)]
(and (ctn/has-any-copy-parent? objects shape)
(not allow-altering-copies))))
@@ -168,9 +168,9 @@
groups-to-unmask
(when-not ignore-mask
(reduce (fn [group-ids id]
;; When the shape to delete is the mask of a masked group,
;; the mask condition must be removed, and it must be
;; converted to a normal group.
;; When the shape to delete is the mask of a masked group,
;; the mask condition must be removed, and it must be
;; converted to a normal group.
(let [obj (lookup id)
parent (lookup (:parent-id obj))]
(if (and (:masked-group parent)
@@ -183,8 +183,8 @@
interacting-shapes
(filter (fn [shape]
;; If any of the deleted shapes is the destination of
;; some interaction, this must be deleted, too.
;; If any of the deleted shapes is the destination of
;; some interaction, this must be deleted, too.
(let [interactions (:interactions shape)]
(some #(and (ctsi/has-destination %)
(contains? ids-to-delete (:destination %)))
@@ -207,7 +207,7 @@
all-parents
(reduce (fn [res id]
;; All parents of any deleted shape must be resized.
;; All parents of any deleted shape must be resized.
(into res (cfh/get-parent-ids objects id)))
(d/ordered-set)
(concat ids-to-delete ids-to-hide))
@@ -239,10 +239,10 @@
(recursive-find-empty-parents parents))))
empty-parents
;; Any parent whose children are all deleted, must be deleted too.
;; If we want to specifically allow altering the copies, this is a special case,
;; for example during a component swap. in this case we are replacing a shape by
;; other one, so must not delete empty parents.
;; Any parent whose children are all deleted, must be deleted too.
;; If we want to specifically allow altering the copies, this is a special case,
;; for example during a component swap. in this case we are replacing a shape by
;; other one, so must not delete empty parents.
(if-not allow-altering-copies
(into (d/ordered-set) (find-all-empty-parents #{}))
#{})
@@ -274,8 +274,8 @@
guides-to-delete)
changes (reduce (fn [changes component-id]
;; It's important to delete the component before the main instance, because we
;; need to store the instance position if we want to restore it later.
;; It's important to delete the component before the main instance, because we
;; need to store the instance position if we want to restore it later.
(pcb/delete-component changes component-id (:id page)))
changes
components-to-delete)
@@ -327,7 +327,7 @@
result #{}]
(if-not current-id
;; Base case, no next element
;; Base case, no next element
result
(let [group (get objects current-id)]
@@ -335,14 +335,14 @@
(not= current-id parent-id)
(empty? (remove removed-id? (:shapes group))))
;; Adds group to the remove and check its parent
;; Adds group to the remove and check its parent
(let [to-check (concat to-check [(cfh/get-parent-id objects current-id)])]
(recur (first to-check)
(rest to-check)
(conj removed-id? current-id)
(conj result current-id)))
;; otherwise recur
;; otherwise recur
(recur (first to-check)
(rest to-check)
removed-id?

View File

@@ -111,7 +111,7 @@
"Check if any ancestor of a shape (between base-parent-id and shape) was swapped"
[shape objects base-parent-id]
(let [ancestors (->> (ctn/get-parent-heads objects shape)
;; Ignore ancestors ahead of base-parent
;; Ignore ancestors ahead of base-parent
(drop-while #(not= base-parent-id (:id %)))
seq)
num-ancestors (count ancestors)

View File

@@ -222,7 +222,7 @@
:else
(cons [node-style (dm/str head-text "" (:text node))] (rest acc)))
;; We add an end-of-line when finish a paragraph
;; We add an end-of-line when finish a paragraph
new-acc
(if (= (:type node) "paragraph")
(let [[hs ht] (first new-acc)]

View File

@@ -458,13 +458,13 @@
(map #(cfh/components-nesting-loop? objects (:id %) (:id parent)))
(every? nil?)))]
(or
;;We don't want to change the structure of component copies
;;We don't want to change the structure of component copies
(ctk/in-component-copy? parent)
(has-any-copy-parent? objects parent)
;; If we are moving something containing a main instance the container can't be part of a component (neither main nor copy)
;; If we are moving something containing a main instance the container can't be part of a component (neither main nor copy)
(and selected-main-instance? parent-in-component?)
;; Avoid placing a shape as a direct or indirect child of itself,
;; or inside its main component if it's in a copy.
;; Avoid placing a shape as a direct or indirect child of itself,
;; or inside its main component if it's in a copy.
comps-nesting-loop?)))
(defn find-valid-parent-and-frame-ids

View File

@@ -775,9 +775,9 @@
file-data (cond-> file-data
(d/not-empty? used-components)
(absorb-components used-components library-data))
;; Note that absorbed components may also be using colors
;; and typographies. This is the reason of doing this first
;; and accumulating file data for the next ones.
;; Note that absorbed components may also be using colors
;; and typographies. This is the reason of doing this first
;; and accumulating file data for the next ones.
used-colors (find-asset-type-usages file-data library-data :color)
file-data (cond-> file-data
@@ -1017,7 +1017,7 @@
libs-to-show
(-> libs-to-show
(add-component library-id component-id))))))
;; (find-used-components-cumulative page root)
;; (find-used-components-cumulative page root)
libs-to-show
components))

View File

@@ -306,7 +306,7 @@
(-write-to [_ heap offset]
(let [buffer' (.-buffer ^js/DataView dbuffer)
;; Calculate byte size: 4 bytes header + (size * FILL-U8-SIZE)
;; Calculate byte size: 4 bytes header + (size * FILL-U8-SIZE)
byte-size (+ 4 (* size FILL-U8-SIZE))
;; Create Uint32Array with exact size needed (convert bytes to u32 elements)
u32-array (js/Uint32Array. buffer' 0 (/ byte-size 4))]

View File

@@ -13,14 +13,15 @@
[app.common.schema :as sm]
[app.common.schema.generators :as sg]))
;; WARNING: options are not deleted when changing event or action type, so it can be
;; restored if the user changes it back later.
;; WARNING: options are not deleted when changing event or action
;; type, so it can be restored if the user changes it back later.
;;
;; But that means that an interaction may have for example a delay or
;; destination, even if its type does not require it (but a previous type did).
;; But that means that an interaction may have for example a delay or
;; destination, even if its type does not require it (but a previous
;; type did).
;;
;; So make sure to use has-delay/has-destination... functions, or similar,
;; before reading them.
;; So make sure to use has-delay/has-destination... functions, or
;; similar, before reading them.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SCHEMA
@@ -452,16 +453,15 @@
(gpt/point 0 0)))
(defn calc-overlay-position
[interaction ;; interaction data
shape ;; Shape with the interaction
objects ;; the objects tree
relative-to-shape ;; the interaction position is realtive to this
;; sape
base-frame ;; the base frame of the current interaction
dest-frame ;; the frame to display with this interaction
frame-offset] ;; if this interaction starts in a frame opened
;; on another interaction, this is the position
;; of that frame
[interaction ; interaction data
shape ; Shape with the interaction
objects ; the objects tree
relative-to-shape ; the interaction position is realtive to this shape
base-frame ; the base frame of the current interaction
dest-frame ; the frame to display with this interaction
frame-offset] ; if this interaction starts in a frame opened
; on another interaction, this is the position
; of that frame
(assert (check-interaction interaction))
(assert (has-overlay-opts interaction)
"expected compatible interaction map")

View File

@@ -382,9 +382,9 @@
keep-ids? (:id shape)
:else (uuid/next))
;; Assign the correct frame-id for the given parent. It's the parent-id (if parent is frame)
;; or the parent's frame-id otherwise. Only for the first cloned shapes. In recursive calls
;; this is not needed.
;; Assign the correct frame-id for the given parent. It's the parent-id (if parent is frame)
;; or the parent's frame-id otherwise. Only for the first cloned shapes. In recursive calls
;; this is not needed.
frame-id (cond
(and (nil? frame-id) (cfh/frame-shape? dest-objects parent-id))
parent-id

View File

@@ -393,7 +393,7 @@
:else
(cons [node-style (dm/str head-text "" (:text node))] (rest acc)))
;; We add an end-of-line when finish a paragraph
;; We add an end-of-line when finish a paragraph
new-acc
(if (= (:type node) "paragraph")
(let [[hs ht] (first new-acc)]

View File

@@ -693,7 +693,7 @@
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
;; Update the attrs on all the content tree
;; Update the attrs on all the content tree
(-> shape
(assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
(assoc-in [:content :children 0 :children 0 :font-size] "32")
@@ -851,7 +851,7 @@
changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(:id main-child)}
(fn [shape]
;; Update the attrs on all the content tree
;; Update the attrs on all the content tree
(-> shape
(assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
(assoc-in [:content :children 0 :children 0 :font-size] "32")

View File

@@ -267,7 +267,7 @@
page' (thf/current-page file')
objects' (:objects page')]
;; ==== Check
;; ==== Check
(thf/validate-file! file')
(t/is (= (count (:components data)) 2))
(t/is (= (count (:components data')) 4))

View File

@@ -141,8 +141,14 @@ http {
proxy_pass http://127.0.0.1:5000;
}
location /nitrate/ {
proxy_pass http://127.0.0.1:3000/;
location /control-center {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /wasm-playground {

View File

@@ -29,8 +29,9 @@ update_flags /var/www/app/js/config.js
export PENPOT_BACKEND_URI=${PENPOT_BACKEND_URI:-http://penpot-backend:6060}
export PENPOT_EXPORTER_URI=${PENPOT_EXPORTER_URI:-http://penpot-exporter:6061}
export PENPOT_NITRATE_URI=${PENPOT_NITRATE_URI:-http://penpot-nitrate:3000}
export PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE=${PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE:-367001600} # Default to 350MiB
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE" \
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_NITRATE_URI,\$PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE" \
< /tmp/nginx.conf.template > /etc/nginx/nginx.conf
PENPOT_DEFAULT_INTERNAL_RESOLVER="$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf)"

View File

@@ -139,6 +139,14 @@ http {
proxy_pass $PENPOT_BACKEND_URI/ws/notifications;
}
location /control-center {
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $http_cf_connecting_ip;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass $PENPOT_NITRATE_URI$request_uri;
}
include /etc/nginx/overrides/server.d/*.conf;
location / {

View File

@@ -12,88 +12,118 @@ test.beforeEach(async ({ page }) => {
await BaseWebSocketPage.mockRPC(page, "get-teams", "get-teams-tokens.json");
});
test.describe("Tokens: Remapping Feature", () => {
const createToken = async (page, type, name, textFieldName, value) => {
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
const { tokensUpdateCreateModal } = await setupTokensFile(page, {
flags: ["enable-token-shadow"],
});
// Create base token
await tokensTabPanel
.getByRole("button", { name: `Add Token: ${type}` })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
const nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill(name);
const colorField = tokensUpdateCreateModal.getByRole("textbox", {
name: textFieldName,
});
await colorField.fill(value);
const submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
};
const renameToken = async (page, oldName, newName) => {
const { tokensUpdateCreateModal, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFile(page, { flags: ["enable-token-shadow"] });
const baseToken = tokensSidebar.getByRole("button", {
name: oldName,
});
await baseToken.click({ button: "right" });
await tokenContextMenuForToken.getByText("Edit token").click();
await expect(tokensUpdateCreateModal).toBeVisible();
const nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill(newName);
const submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
};
const createCompositeDerivedToken = async (page, type, name, reference) => {
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
const { tokensUpdateCreateModal } = await setupTokensFile(page, {
flags: ["enable-token-shadow"],
});
await tokensTabPanel
.getByRole("button", { name: `Add Token: ${type}` })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
const nameField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Name",
});
await nameField.fill(name);
const referenceToggle = tokensUpdateCreateModal.getByTestId("reference-opt");
await referenceToggle.click();
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Reference",
});
await referenceField.fill(reference);
const submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
};
test.describe("Remapping Tokens", () => {
test.describe("Box Shadow Token Remapping", () => {
test("User renames box shadow token with alias references", async ({
page,
}) => {
const {
tokensUpdateCreateModal,
tokensSidebar,
tokenContextMenuForToken,
} = await setupTokensFile(page, { flags: ["enable-token-shadow"] });
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
const { tokensSidebar } = await setupTokensFile(page, {
flags: ["enable-token-shadow"],
});
// Create base shadow token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Shadow" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
let nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("base-shadow");
const colorField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Color",
});
await colorField.fill("#000000");
let submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
await createToken(page, "Shadow", "base-shadow", "Color", "#000000");
// Create derived shadow token that references base-shadow
await tokensTabPanel
.getByRole("button", { name: "Add Token: Shadow" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Name",
});
await nameField.fill("derived-shadow");
const referenceToggle =
tokensUpdateCreateModal.getByTestId("reference-opt");
await referenceToggle.click();
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Reference",
});
await referenceField.fill("{base-shadow}");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
await createCompositeDerivedToken(
page,
"Shadow",
"derived-shadow",
"{base-shadow}",
);
// Rename base-shadow token
const baseToken = tokensSidebar.getByRole("button", {
name: "base-shadow",
});
await baseToken.click({ button: "right" });
await tokenContextMenuForToken.getByText("Edit token").click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("foundation-shadow");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await renameToken(page, "base-shadow", "foundation-shadow");
// Check for remapping modal
const remappingModal = page.getByTestId("token-remapping-modal");
await expect(remappingModal).toBeVisible({ timeout: 5000 });
await expect(remappingModal).toContainText("1");
await expect(remappingModal).toContainText("base-shadow");
await expect(remappingModal).toContainText("foundation-shadow");
const confirmButton = remappingModal.getByRole("button", {
name: /remap/i,
name: "remap tokens",
});
await confirmButton.click();
@@ -116,51 +146,16 @@ test.describe("Tokens: Remapping Feature", () => {
workspacePage,
} = await setupTokensFile(page, { flags: ["enable-token-shadow"] });
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
// Create base shadow token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Shadow" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
let nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("primary-shadow");
let colorField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Color",
});
await colorField.fill("#000000");
let submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
await createToken(page, "Shadow", "primary-shadow", "Color", "#000000");
// Create derived shadow token that references base
await tokensTabPanel
.getByRole("button", { name: "Add Token: Shadow" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("card-shadow");
const referenceToggle =
tokensUpdateCreateModal.getByTestId("reference-opt");
await referenceToggle.click();
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Reference",
});
await referenceField.fill("{primary-shadow}");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
await createCompositeDerivedToken(
page,
"Shadow",
"card-shadow",
"{primary-shadow}",
);
// Apply the referenced token to a shape
await page.getByRole("tab", { name: "Layers" }).click();
@@ -183,16 +178,16 @@ test.describe("Tokens: Remapping Feature", () => {
await tokenContextMenuForToken.getByText("Edit token").click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByLabel("Name");
const nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("main-shadow");
// Update the color value
colorField = tokensUpdateCreateModal.getByRole("textbox", {
const colorField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Color",
});
await colorField.fill("#FF0000");
submitButton = tokensUpdateCreateModal.getByRole("button", {
const submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
@@ -202,7 +197,7 @@ test.describe("Tokens: Remapping Feature", () => {
await expect(remappingModal).toBeVisible({ timeout: 5000 });
const confirmButton = remappingModal.getByRole("button", {
name: /remap/i,
name: "remap tokens",
});
await confirmButton.click();
@@ -259,73 +254,25 @@ test.describe("Tokens: Remapping Feature", () => {
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
// Create base typography token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Typography" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
let nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("base-text");
const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Font size",
});
await fontSizeField.fill("16");
let submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
await createToken(page, "Typography", "base-text", "Font size", "16");
// Create derived typography token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Typography" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Name",
});
await nameField.fill("body-text");
const referenceToggle =
tokensUpdateCreateModal.getByTestId("reference-opt");
await referenceToggle.click();
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Reference",
});
await referenceField.fill("{base-text}");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
await createCompositeDerivedToken(
page,
"Typography",
"body-text",
"{base-text}",
);
// Rename base token
const baseToken = tokensSidebar.getByRole("button", {
name: "base-text",
});
await baseToken.click({ button: "right" });
await tokenContextMenuForToken.getByText("Edit token").click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("default-text");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await renameToken(page, "base-text", "default-text");
// Check for remapping modal
const remappingModal = page.getByTestId("token-remapping-modal");
await expect(remappingModal).toBeVisible({ timeout: 5000 });
const confirmButton = remappingModal.getByRole("button", {
name: /remap/i,
name: "remap tokens",
});
await confirmButton.click();
@@ -351,24 +298,7 @@ test.describe("Tokens: Remapping Feature", () => {
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
// Create base typography token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Typography" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
let nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("body-style");
let fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Font size",
});
await fontSizeField.fill("16");
let submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
await createToken(page, "Typography", "body-style", "Font size", "16");
// Create derived typography token
await tokensTabPanel
@@ -376,7 +306,7 @@ test.describe("Tokens: Remapping Feature", () => {
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByRole("textbox", {
let nameField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Name",
});
await nameField.fill("paragraph-style");
@@ -390,7 +320,7 @@ test.describe("Tokens: Remapping Feature", () => {
});
await referenceField.fill("{body-style}");
submitButton = tokensUpdateCreateModal.getByRole("button", {
let submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
@@ -421,7 +351,7 @@ test.describe("Tokens: Remapping Feature", () => {
await nameField.fill("text-base");
// Update the font size value
fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Font size",
});
await fontSizeField.fill("18");
@@ -436,7 +366,7 @@ test.describe("Tokens: Remapping Feature", () => {
await expect(remappingModal).toBeVisible({ timeout: 5000 });
const confirmButton = remappingModal.getByRole("button", {
name: /remap/i,
name: "remap tokens",
});
await confirmButton.click();
@@ -471,72 +401,29 @@ test.describe("Tokens: Remapping Feature", () => {
test("User renames border radius token with alias references", async ({
page,
}) => {
const {
tokensUpdateCreateModal,
tokensSidebar,
tokenContextMenuForToken,
} = await setupTokensFile(page);
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
const { tokensSidebar } = await setupTokensFile(page);
// Create base border radius token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Border Radius" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
let nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("base-radius");
const valueField = tokensUpdateCreateModal.getByLabel("Value");
await valueField.fill("4");
let submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
await createToken(page, "Border Radius", "base-radius", "Value", "4");
// Create derived border radius token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Border Radius" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("card-radius");
const valueField2 = tokensUpdateCreateModal.getByLabel("Value");
await valueField2.fill("{base-radius}");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
await createToken(
page,
"Border Radius",
"card-radius",
"Value",
"{base-radius}",
);
// Rename base token
const baseToken = tokensSidebar.getByRole("button", {
name: "base-radius",
});
await baseToken.click({ button: "right" });
await tokenContextMenuForToken.getByText("Edit token").click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("primary-radius");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await renameToken(page, "base-radius", "primary-radius");
// Check for remapping modal
const remappingModal = page.getByTestId("token-remapping-modal");
await expect(remappingModal).toBeVisible({ timeout: 5000 });
const confirmButton = remappingModal.getByRole("button", {
name: /remap/i,
name: "remap tokens",
});
await confirmButton.click();
@@ -558,43 +445,17 @@ test.describe("Tokens: Remapping Feature", () => {
tokenContextMenuForToken,
} = await setupTokensFile(page);
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
// Create base border radius token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Border Radius" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
let nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("radius-sm");
let valueField = tokensUpdateCreateModal.getByLabel("Value");
await valueField.fill("4");
let submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
await createToken(page, "Border Radius", "radius-sm", "Value", "4");
// Create derived border radius token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Border Radius" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("button-radius");
const valueField2 = tokensUpdateCreateModal.getByLabel("Value");
await valueField2.fill("{radius-sm}");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
await createToken(
page,
"Border Radius",
"button-radius",
"Value",
"{radius-sm}",
);
// Rename and update value of base token
const radiusToken = tokensSidebar.getByRole("button", {
@@ -604,14 +465,14 @@ test.describe("Tokens: Remapping Feature", () => {
await tokenContextMenuForToken.getByText("Edit token").click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByLabel("Name");
const nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("radius-base");
// Update the value
valueField = tokensUpdateCreateModal.getByLabel("Value");
const valueField = tokensUpdateCreateModal.getByLabel("Value");
await valueField.fill("8");
submitButton = tokensUpdateCreateModal.getByRole("button", {
const submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
@@ -621,7 +482,7 @@ test.describe("Tokens: Remapping Feature", () => {
await expect(remappingModal).toBeVisible({ timeout: 5000 });
const confirmButton = remappingModal.getByRole("button", {
name: /remap/i,
name: "remap tokens",
});
await confirmButton.click();
@@ -648,4 +509,82 @@ test.describe("Tokens: Remapping Feature", () => {
await expect(currentValue).toHaveValue("{radius-base}");
});
});
test.describe("Cancel remap", () => {
test("Only rename - breaks reference", async ({ page }) => {
const { tokensSidebar } = await setupTokensFile(page, {
flags: ["enable-token-shadow"],
});
// Create base shadow token
await createToken(page, "Shadow", "base-shadow", "Color", "#000000");
// Create derived shadow token that references base-shadow
await createCompositeDerivedToken(
page,
"Shadow",
"derived-shadow",
"{base-shadow}",
);
// Rename base-shadow token
await renameToken(page, "base-shadow", "foundation-shadow");
// Check for remapping modal
const remappingModal = page.getByTestId("token-remapping-modal");
await expect(remappingModal).toBeVisible({ timeout: 5000 });
const cancelButton = remappingModal.getByRole("button", {
name: "don't remap",
});
await cancelButton.click();
// Verify token was renamed
await expect(
tokensSidebar.getByRole("button", {
name: "foundation-shadow",
}),
).toBeVisible();
await expect(
tokensSidebar.locator('[aria-label="Missing reference"]'),
).toBeVisible();
});
test("Cancel process - no changes applied", async ({ page }) => {
const { tokensSidebar } = await setupTokensFile(page, {
flags: ["enable-token-shadow"],
});
// Create base shadow token
await createToken(page, "Shadow", "base-shadow", "Color", "#000000");
// Create derived shadow token that references base-shadow
await createCompositeDerivedToken(
page,
"Shadow",
"derived-shadow",
"{base-shadow}",
);
// Rename base-shadow token
await renameToken(page, "base-shadow", "foundation-shadow");
// Check for remapping modal
const remappingModal = page.getByTestId("token-remapping-modal");
await expect(remappingModal).toBeVisible({ timeout: 5000 });
const closeButton = remappingModal.getByRole("button", {
name: "close",
});
await closeButton.click();
// Verify original token name still exists
await expect(
tokensSidebar.getByRole("button", { name: "base-shadow" }),
).toBeVisible();
await expect(
tokensSidebar.getByRole("button", { name: "derived-shadow" }),
).toBeVisible();
});
});
});

View File

@@ -216,4 +216,32 @@ test.describe("Tokens: Sets Tab", () => {
await expect(tokenSetItems.nth(1)).toHaveAttribute("aria-checked", "false");
await expect(tokenSetItems.nth(2)).toHaveAttribute("aria-checked", "true");
});
test("Display active set and verify if is enabled", async ({ page }) => {
const { tokenThemesSetsSidebar, tokensSidebar, tokenSetItems } =
await setupTokensFile(page);
// Create set
await tokenThemesSetsSidebar
.getByRole("button", { name: "Add set" })
.click();
await changeSetInput(tokenThemesSetsSidebar, "Inactive set");
await tokenThemesSetsSidebar
.getByRole("button", { name: "Inactive set" })
.click();
let activeSetTitle = await tokensSidebar.getByTestId(
"active-token-set-title",
);
await expect(activeSetTitle).toHaveText("TOKENS - Inactive set");
const inactiveSetInfo = await tokensSidebar.getByTitle(
"This set is not active.",
);
await expect(inactiveSetInfo).toBeVisible();
// Switch active set
await tokenThemesSetsSidebar.getByRole("button", { name: "theme" }).click();
await expect(activeSetTitle).toHaveText("TOKENS - theme");
await expect(inactiveSetInfo).not.toBeVisible();
});
});

View File

@@ -15,6 +15,7 @@
[app.common.time :as ct]
[app.common.types.project :refer [valid-project?]]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.constants :as mconst]
[app.main.data.common :as dcm]
[app.main.data.event :as ev]
@@ -80,7 +81,7 @@
ptk/UpdateEvent
(update [_ state]
(reduce (fn [state {:keys [id] :as project}]
;; Replace completely instead of merge to ensure deleted-at is removed
;; Replace completely instead of merge to ensure deleted-at is removed
(assoc-in state [:projects id] project))
state
projects))))
@@ -683,12 +684,25 @@
(rx/of (dcm/change-team-role params)
(modal/hide)))))
(defn handle-change-team-org
[{:keys [team-id organization-id organization-name]}]
(ptk/reify ::handle-change-team-org
ptk/UpdateEvent
(update [_ state]
(if (contains? cf/flags :nitrate)
(d/update-in-when state [:teams team-id] assoc
:organization-id organization-id
:organization-name organization-name)
state))))
(defn- process-message
[{:keys [type] :as msg}]
(case type
:notification (dcm/handle-notification msg)
:team-role-change (handle-change-team-role msg)
:team-membership-change (dcm/team-membership-change msg)
:team-org-change (handle-change-team-org msg)
nil))

View File

@@ -104,15 +104,15 @@
variant (or (.getEnglishName ^js font "preferredSubfamily")
(.getEnglishName ^js font "fontSubfamily"))
;; Vertical metrics determine the baseline in a text and the space between lines of
;; text. For historical reasons, there are three pairs of ascender/descender
;; values, known as hhea, OS/2 and uSWin metrics. Depending on the font, operating
;; system and application a different set will be used to render text on the
;; screen. On Mac, Safari and Chrome use the hhea values to render text. Firefox
;; will respect the useTypoMetrics setting and will use the OS/2 if it is set. If
;; the useTypoMetrics is not set, Firefox will also use metrics from the hhea
;; table. On Windows, all browsers use the usWin metrics, but respect the
;; useTypoMetrics setting and if set will use the OS/2 values.
;; Vertical metrics determine the baseline in a text and the space between lines of
;; text. For historical reasons, there are three pairs of ascender/descender
;; values, known as hhea, OS/2 and uSWin metrics. Depending on the font, operating
;; system and application a different set will be used to render text on the
;; screen. On Mac, Safari and Chrome use the hhea values to render text. Firefox
;; will respect the useTypoMetrics setting and will use the OS/2 if it is set. If
;; the useTypoMetrics is not set, Firefox will also use metrics from the hhea
;; table. On Windows, all browsers use the usWin metrics, but respect the
;; useTypoMetrics setting and if set will use the OS/2 values.
hhea-ascender (abs (-> ^js font .-tables .-hhea .-ascender))
hhea-descender (abs (-> ^js font .-tables .-hhea .-descender))

View File

@@ -156,8 +156,8 @@
(defn- update-plugin-permissions-peek
[{:keys [plugin-id url]}]
(when url
;; If the saved manifest has a URL we fetch the manifest to check
;; for updates
;; If the saved manifest has a URL we fetch the manifest to check
;; for updates
(->> (fetch-manifest url)
(rx/subs!
(fn [new-manifest]

View File

@@ -410,25 +410,25 @@
(string? value)
{:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value-shadow value)]}
;; Empty value
;; Empty value
(nil? value) {:errors [(wte/get-error-code :error.token/empty-input)]}
;; Invalid value
;; Invalid value
(not (js/Array.isArray value)) {:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]}
;; Array of shadows
;; Array of shadows
:else
(let [converted (js->clj value :keywordize-keys true)
;; Parse each shadow with its index
;; Parse each shadow with its index
parsed-shadows (map-indexed
(fn [idx shadow-map]
(parse-single-shadow shadow-map idx))
converted)
;; Collect all errors from all shadows
;; Collect all errors from all shadows
all-errors (mapcat :errors parsed-shadows)
;; Collect all values from shadows that have values
;; Collect all values from shadows that have values
all-values (into [] (keep :value parsed-shadows))]
(if (seq all-errors)

View File

@@ -1292,9 +1292,9 @@
(rx/take 1 workspace-data-s)
(rx/take 1 workspace-data-s)
workspace-data-s)
;; Need to get the file data before the change, so deleted shapes
;; still exist, for example. We initialize the buffer with three
;; copies of the initial state
;; Need to get the file data before the change, so deleted shapes
;; still exist, for example. We initialize the buffer with three
;; copies of the initial state
(rx/buffer 3 1))
changes-s

View File

@@ -231,12 +231,12 @@
:timeout nil
:tag :media-loading}))
(->> (if (seq uris)
;; Media objects is a list of URL's pointing to the path
;; Media objects is a list of URL's pointing to the path
(process-uris params)
;; Media objects are blob of data to be upload
;; Media objects are blob of data to be upload
(process-blobs params))
;; Every stream has its own sideeffect. We need to ignore the result
;; Every stream has its own sideeffect. We need to ignore the result
(rx/ignore)
(rx/catch #(handle-media-error % on-error))
(rx/finalize #(st/emit! (ntf/hide :tag :media-loading))))))))

View File

@@ -793,8 +793,8 @@
(-> options
(assoc :reg-objects? true)
(assoc :ignore-tree ignore-tree)
;; Attributes that can change in the transform. This
;; way we don't have to check all the attributes
;; Attributes that can change in the transform. This
;; way we don't have to check all the attributes
(assoc :attrs transform-attrs))
update-shape

View File

@@ -439,7 +439,7 @@
add-new-variant? (and
;; The parent is a variant container
(-> parent-id objects ctc/is-variant-container?)
;; Any of the shapes is a main instance
;; Any of the shapes is a main instance
(some (comp ctc/main-instance? objects) ids))
undo-id (js/Symbol)]

View File

@@ -143,7 +143,7 @@
([value shape-ids attributes]
(update-stroke-color value shape-ids attributes nil))
;; The attributes param is needed to have the same arity that other update functions
;; The attributes param is needed to have the same arity that other update functions
([value shape-ids _attributes page-id]
(when-let [color (value->color value)]
(dwsh/update-shapes shape-ids

View File

@@ -1121,15 +1121,15 @@
:cell cell))
add-component-to-variant? (and
;; Any of the shapes is a head
;; Any of the shapes is a head
(some (comp ctk/instance-head? objects) ids)
;; Any ancestor of the destination parent is a variant
(->> (cfh/get-parents-with-self objects frame-id)
(some ctk/is-variant?)))
add-new-variant? (and
;; The parent is a variant container
;; The parent is a variant container
(-> frame-id objects ctk/is-variant-container?)
;; Any of the shapes is a main instance
;; Any of the shapes is a main instance
(some (comp ctk/main-instance? objects) ids))]
(rx/concat

View File

@@ -447,8 +447,8 @@
:stroke-opacity 1
:stroke-width 2}
;; Move the position of the variant container so the main shape doesn't
;; change its position
;; Move the position of the variant container so the main shape doesn't
;; change its position
delta (or delta
(if (ctsl/any-layout? parent)
(gpt/point 0 0)
@@ -456,8 +456,8 @@
undo-id (js/Symbol)]
;;TODO Refactor all called methods in order to be able to
;;generate changes instead of call the events
;;TODO Refactor all called methods in order to be able to
;;generate changes instead of call the events
(rx/concat
@@ -467,7 +467,7 @@
(when (not= name (:name main))
(dwl/rename-component component-id name))
;; Create variant container
;; Create variant container
(dwsh/create-artboard-from-shapes [main-instance-id] variant-id nil nil nil delta flex?)
(cl/remove-all-fills variant-vec {:color clr/black :opacity 1})
(when flex? (dwsl/create-layout-from-id variant-id :flex))
@@ -476,12 +476,12 @@
(cl/add-stroke variant-vec stroke-props)
(set-variant-id component-id variant-id))
;; Add the necessary number of new properties, with default values
;; Add the necessary number of new properties, with default values
(rx/from
(repeatedly num-props
#(add-new-property variant-id {:fill-values? true})))
;; When the component has path, set the path items as properties values
;; When the component has path, set the path items as properties values
(when (> (count cpath) 1)
(rx/from
(map
@@ -634,7 +634,7 @@
prefix (->> shapes
(mapv #(cpn/split-path (:name %)))
(common-prefix))
;; When the common parent is root, add a wrapper
;; When the common parent is root, add a wrapper
add-wrapper? (empty? prefix)
first-shape (first shapes)
delta (gpt/point (- (:x rect) (:x first-shape) 30)
@@ -667,10 +667,10 @@
(dwt/update-dimensions [variant-id] :height (+ (:height rect) 60))
(ev/event {::ev/name "combine-as-variants" ::ev/origin trigger :number-of-combined (count ids)}))
;; NOTE: we need to schedule a commit into a
;; microtask for ensure that all the scheduled
;; microtask of previous events execute before the
;; commit
;; NOTE: we need to schedule a commit into a
;; microtask for ensure that all the scheduled
;; microtask of previous events execute before the
;; commit
(->> (rx/of (dwu/commit-undo-transaction undo-id))
(rx/observe-on :async)))))))
@@ -705,7 +705,7 @@
(let [libraries (dsh/lookup-libraries state)
component-id (:component-id shape)
component (ctf/get-component libraries (:component-file shape) component-id :include-deleted? false)]
;; If the value is already val, do nothing
;; If the value is already val, do nothing
(when (not= val (dm/get-in component [:variant-properties pos :value]))
(let [current-page-objects (dsh/lookup-page-objects state)
variant-id (:variant-id component)

View File

@@ -214,8 +214,8 @@
([font-id variant-id]
(log/dbg :action "try-ensure-loaded!" :font-id font-id :variant-id variant-id)
(if-not (exists? js/window)
;; If we are in the worker environment, we just mark it as loaded
;; without really loading it.
;; If we are in the worker environment, we just mark it as loaded
;; without really loading it.
(do
(swap! loaded-hints conj {:font-id font-id :font-variant-id variant-id})
(p/resolved font-id))

View File

@@ -133,7 +133,7 @@
[{:keys [shape] :as props}]
(let [childs (mapv #(get objects %) (:shapes shape))]
(if (and (map? (:content shape))
;; tspan shouldn't be contained in a group or have svg defs
;; tspan shouldn't be contained in a group or have svg defs
(not= :tspan (get-in shape [:content :tag]))
(or (= :svg (get-in shape [:content :tag]))
(contains? shape :svg-attrs)))

View File

@@ -762,7 +762,7 @@
h (:height viewport)
comment-width 284 ;; TODO: this is the width set via CSS in an outer container…
;; We should probably do this in a different way.
;; We should probably do this in a different way.
orientation-left? (>= (+ base-x comment-width (:x bubble-margin)) w)
orientation-top? (>= base-y (/ h 2))

View File

@@ -198,7 +198,7 @@
:valid (and touched? (not error))
:invalid (and touched? error)
:disabled disabled)
;; :empty (str/empty? value)
;; :empty (str/empty? value)
on-focus #(reset! focus? true)

View File

@@ -35,6 +35,7 @@
[app.main.ui.dashboard.team-form]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.icons :as deprecated-icon]
[app.main.ui.nitrate.nitrate-form]
[app.util.dom :as dom]
[app.util.dom.dnd :as dnd]
[app.util.i18n :as i18n :refer [tr]]
@@ -280,8 +281,8 @@
(mf/defc teams-selector-dropdown*
{::mf/private true}
[{:keys [team profile teams] :rest props}]
(let [on-create-click
[{:keys [team profile teams show-default-team allow-create-teams allow-create-org] :rest props}]
(let [on-create-team-click
(mf/use-fn #(st/emit! (modal/show :team-form {})))
on-team-click
@@ -290,18 +291,27 @@
(let [team-id (-> (dom/get-current-target event)
(dom/get-data "value")
(uuid/parse))]
(st/emit! (dcm/go-to-dashboard-recent :team-id team-id)))))]
(st/emit! (dcm/go-to-dashboard-recent :team-id team-id)))))
on-create-org-click
(mf/use-fn
(fn []
(if (:nitrate-licence profile)
;; TODO update when org creation route is ready
(dom/open-new-window "/control-center/org/create")
(st/emit! (modal/show :nitrate-form {})))))]
[:> dropdown-menu* props
[:> dropdown-menu-item* {:on-click on-team-click
:data-value (:default-team-id profile)
:class (stl/css :team-dropdown-item)}
[:span {:class (stl/css :penpot-icon)} deprecated-icon/logo-icon]
(when show-default-team
[:> dropdown-menu-item* {:on-click on-team-click
:data-value (:default-team-id profile)
:class (stl/css :team-dropdown-item)}
[:span {:class (stl/css :penpot-icon)} deprecated-icon/logo-icon]
[:span {:class (stl/css :team-text)} (tr "dashboard.your-penpot")]
(when (= (:default-team-id profile) (:id team))
tick-icon)]
[:span {:class (stl/css :team-text)} (tr "dashboard.your-penpot")]
(when (= (:default-team-id profile) (:id team))
tick-icon)])
(for [team-item (remove :is-default (vals teams))]
[:> dropdown-menu-item* {:on-click on-team-click
@@ -322,11 +332,19 @@
(when (= (:id team-item) (:id team))
tick-icon)])
[:hr {:role "separator" :class (stl/css :team-separator)}]
[:> dropdown-menu-item* {:on-click on-create-click
:class (stl/css :team-dropdown-item :action)}
[:span {:class (stl/css :icon-wrapper)} add-icon]
[:span {:class (stl/css :team-text)} (tr "dashboard.create-new-team")]]]))
(when allow-create-teams
[:hr {:role "separator" :class (stl/css :team-separator)}]
[:> dropdown-menu-item* {:on-click on-create-team-click
:class (stl/css :team-dropdown-item :action)}
[:span {:class (stl/css :icon-wrapper)} add-icon]
[:span {:class (stl/css :team-text)} (tr "dashboard.create-new-team")]])
(when allow-create-org
[:hr {:role "separator" :class (stl/css :team-separator)}]
[:> dropdown-menu-item* {:on-click on-create-org-click
:class (stl/css :team-dropdown-item :action)}
[:span {:class (stl/css :icon-wrapper)} add-icon]
[:span {:class (stl/css :team-text)} (tr "dashboard.create-new-org")]])]))
(mf/defc team-options-dropdown*
{::mf/private true}
@@ -476,9 +494,80 @@
:data-testid "delete-team"}
(tr "dashboard.delete-team")])]))
(mf/defc sidebar-org-switch*
[{:keys [team profile]}]
(let [teams (->> (mf/deref refs/teams)
vals
(group-by :organization-id)
(map (fn [[_group entries]] (first entries)))
vec
(d/index-by :id))
teams (update-vals teams
(fn [t]
(assoc t :name (str "ORG: " (:organization-name t)))))
team (assoc team :name (str "ORG: " (:organization-name team)))
show-teams-menu*
(mf/use-state false)
show-teams-menu?
(deref show-teams-menu*)
on-show-teams-click
(mf/use-fn
(fn [event]
(dom/stop-propagation event)
(swap! show-teams-menu* not)))
on-show-teams-keydown
(mf/use-fn
(fn [event]
(when (or (kbd/space? event)
(kbd/enter? event))
(dom/prevent-default event)
(dom/stop-propagation event)
(some-> (dom/get-current-target event)
(dom/click!)))))
close-teams-menu
(mf/use-fn #(reset! show-teams-menu* false))]
[:div {:class (stl/css :sidebar-team-switch)}
[:div {:class (stl/css :switch-content)}
[:button {:class (stl/css :current-team)
:on-click on-show-teams-click
:on-key-down on-show-teams-keydown}
[:div {:class (stl/css :team-name)}
[:img {:src (cf/resolve-team-photo-url team)
:class (stl/css :team-picture)
:alt (:name team)}]
[:span {:class (stl/css :team-text) :title (:name team)} (:name team)]]
arrow-icon]]
;; Teams Dropdown
[:> teams-selector-dropdown* {:show show-teams-menu?
:on-close close-teams-menu
:id "organizations-list"
:class (stl/css :dropdown :teams-dropdown)
:team team
:profile profile
:teams teams
:show-default-team false
:allow-create-teams false
:allow-create-org true}]]))
(mf/defc sidebar-team-switch*
[{:keys [team profile]}]
(let [teams (mf/deref refs/teams)
(let [nitrate? (contains? cf/flags :nitrate)
org-id (when nitrate? (:organization-id team))
teams (cond->> (mf/deref refs/teams)
nitrate?
(filter #(= (-> % val :organization-id) org-id)))
subscription
(get team :subscription)
@@ -586,7 +675,10 @@
:class (stl/css :dropdown :teams-dropdown)
:team team
:profile profile
:teams teams}]
:teams teams
:show-default-team true
:allow-create-teams true
:allow-create-org false}]
[:> team-options-dropdown* {:show show-team-options-menu?
:on-close close-team-options-menu
@@ -703,6 +795,8 @@
[:*
[:div {:class (stl/css-case :sidebar-content true)
:ref container}
(when (contains? cf/flags :nitrate)
[:> sidebar-org-switch* {:team team :profile profile}])
[:> sidebar-team-switch* {:team team :profile profile}]
[:> sidebar-search* {:search-term search-term

View File

@@ -225,10 +225,10 @@
(and
(= subscription-type "unlimited")
(or
;; common: seats < 25 and diff >= 4
;; common: seats < 25 and diff >= 4
(and (< seats 25)
(>= (- editors seats) 4))
;; special: reached 25+ editors, seats < 25 and there is overuse
;; special: reached 25+ editors, seats < 25 and there is overuse
(and (< seats 25)
(>= editors 25)
(> editors seats)))))))

View File

@@ -135,8 +135,8 @@
:version "1.1"
:xmlns "http://www.w3.org/2000/svg"
:xmlnsXlink "http://www.w3.org/1999/xlink"
;; Fix Chromium bug about color of html texts
;; https://bugs.chromium.org/p/chromium/issues/detail?id=1244560#c5
;; Fix Chromium bug about color of html texts
;; https://bugs.chromium.org/p/chromium/issues/detail?id=1244560#c5
:style {:-webkit-print-color-adjust :exact}
:fill "none"}

View File

@@ -71,11 +71,11 @@
start-size-ref (mf/use-ref nil)
start-ref (mf/use-ref nil)
;; Since Penpot is not responsive designed, this value will only refer to vertical axis.
;; Since Penpot is not responsive designed, this value will only refer to vertical axis.
window-height* (mf/use-state #(dom/get-window-height))
window-height (deref window-height*)
;; In case max-val is a string, we need to parse it as a double.
;; In case max-val is a string, we need to parse it as a double.
max-val (mf/with-memo [max-val window-height]
(let [parsed-max-val (when (string? max-val) (d/parse-double max-val))]
(if parsed-max-val

View File

@@ -82,7 +82,7 @@
:style #js {"--bullet-size" "16px"}}
[:& cb/color-bullet {:color color
:mini true}]]
;; REMOVE this conditional when :inspect-styles flag is removed
;; REMOVE this conditional when :inspect-styles flag is removed
(if (contains? cf/flags :inspect-styles)
[:div {:class (stl/css :global/attr-label)} property]
[:div {:class (stl/css :format-wrapper)}

View File

@@ -107,10 +107,10 @@
(when (:text-decoration style)
[:div {:class (stl/css :text-row)}
[:div {:class (stl/css :global/attr-label)} "Text Decoration"]
;; Execution time translation strings:
;; (tr "inspect.attributes.typography.text-decoration.none")
;; (tr "inspect.attributes.typography.text-decoration.strikethrough")
;; (tr "inspect.attributes.typography.text-decoration.underline")
;; Execution time translation strings:
;; (tr "inspect.attributes.typography.text-decoration.none")
;; (tr "inspect.attributes.typography.text-decoration.strikethrough")
;; (tr "inspect.attributes.typography.text-decoration.underline")
[:div {:class (stl/css :global/attr-value)}
[:> copy-button* {:data (copy-style-data style :text-decoration)}
[:div {:class (stl/css :button-children)}
@@ -119,12 +119,12 @@
(when (:text-transform style)
[:div {:class (stl/css :text-row)}
[:div {:class (stl/css :global/attr-label)} "Text Transform"]
;; Execution time translation strings:
;; (tr "inspect.attributes.typography.text-transform.lowercase")
;; (tr "inspect.attributes.typography.text-transform.none")
;; (tr "inspect.attributes.typography.text-transform.capitalize")
;; (tr "inspect.attributes.typography.text-transform.uppercase")
;; (tr "inspect.attributes.typography.text-transform.unset")
;; Execution time translation strings:
;; (tr "inspect.attributes.typography.text-transform.lowercase")
;; (tr "inspect.attributes.typography.text-transform.none")
;; (tr "inspect.attributes.typography.text-transform.capitalize")
;; (tr "inspect.attributes.typography.text-transform.uppercase")
;; (tr "inspect.attributes.typography.text-transform.unset")
[:div {:class (stl/css :global/attr-value)}
[:> copy-button* {:data (copy-style-data style :text-transform)}
[:div {:class (stl/css :button-children)}

View File

@@ -141,14 +141,14 @@
(for [panel panels]
[:li {:key (d/name panel)}
(case panel
;; VARIANTS PANEL
;; VARIANTS PANEL
:variant
[:> style-box* {:panel :variant}
[:> variants-panel* {:component first-component
:objects objects
:shape first-shape
:data data}]]
;; GEOMETRY PANEL
;; GEOMETRY PANEL
:geometry
[:> style-box* {:panel :geometry
:shorthand (:geometry shorthands)}
@@ -156,7 +156,7 @@
:objects objects
:resolved-tokens resolved-active-tokens
:on-geometry-shorthand set-shorthands}]]
;; LAYOUT PANEL
;; LAYOUT PANEL
:layout
(let [layout-shapes (->> shapes (filter ctl/any-layout?))]
(when (seq layout-shapes)
@@ -166,7 +166,7 @@
:objects objects
:resolved-tokens resolved-active-tokens
:on-layout-shorthand set-shorthands}]]))
;; LAYOUT ELEMENT PANEL
;; LAYOUT ELEMENT PANEL
:layout-element
(let [shapes (->> shapes (filter #(ctl/any-layout-immediate-child? objects %)))
some-layout-prop? (->> shapes

View File

@@ -101,7 +101,7 @@
:resolved-tokens resolved-tokens
:color-space color-space}]))
;; Typography style
;; Typography style
(when (and (not composite-typography-token)
(:typography-ref-id style))
[:> typography-name-block* {:style style}])

View File

@@ -0,0 +1,48 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.nitrate.nitrate-form
(:require-macros [app.main.style :as stl])
(:require
[app.main.data.modal :as modal]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.icons :as deprecated-icon]
[app.util.dom :as dom]
[rumext.v2 :as mf]))
;; FIXME: rename to `form` (remove the nitrate prefix from namespace,
;; because it is already under nitrate)
(mf/defc nitrate-form-modal*
{::mf/register modal/components
::mf/register-as :nitrate-form}
[]
(let [on-click
(mf/use-fn
(fn []
(dom/open-new-window "/control-center/licenses/start")))]
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-container)}
[:div {:class (stl/css :nitrate-form)}
[:div {:class (stl/css :modal-header)}
[:h2 {:class (stl/css :modal-title)}
"BUY NITRATE"]
[:button {:class (stl/css :modal-close-btn)
:on-click modal/hide!} deprecated-icon/close]]
[:div {:class (stl/css :modal-content)}
"Nitrate is so cool! You should buy it!"]
[:div {:class (stl/css :modal-footer)}
[:div {:class (stl/css :action-buttons)}
[:> button* {:variant "primary"
:on-click on-click}
"BUY NOW!"]]]]]]))

View File

@@ -0,0 +1,52 @@
// 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
@use "refactor/common-refactor.scss" as deprecated;
.modal-overlay {
@extend .modal-overlay-base;
}
.modal-container {
@extend .modal-container-base;
}
.modal-header {
margin-bottom: deprecated.$s-24;
}
.modal-title {
@include deprecated.uppercaseTitleTipography;
color: var(--modal-title-foreground-color);
}
.modal-close-btn {
@extend .modal-close-btn-base;
}
.modal-content {
margin-bottom: deprecated.$s-24;
}
.nitrate-form {
min-width: deprecated.$s-400;
}
.action-buttons {
@extend .modal-action-btns;
}
.cancel-button {
@extend .modal-cancel-btn;
}
.accept-btn {
@extend .modal-accept-btn;
&.danger {
@extend .modal-danger-btn;
}
}

View File

@@ -61,7 +61,7 @@
content
(when (some? links)
(for [[index link] (d/enumerate links)]
;; TODO Review this component
;; TODO Review this component
[:& lb/link-button {:class (stl/css :link)
:on-click (:callback link)
:value (:label link)

View File

@@ -171,8 +171,8 @@
:fillOpacity opacity}
[:path {:d "M 3 0 L 6 3 L 3 6 L 0 3 z"}]])
;; If the user wants line caps but different in each end,
;; simulate it with markers.
;; If the user wants line caps but different in each end,
;; simulate it with markers.
(when (and (or (= cap-start :round)
(= cap-end :round))
(not= cap-start cap-end))

View File

@@ -100,14 +100,14 @@
[:& filters/filters {:shape (dissoc shape :blur) :filter-id filter-id-shadows}]
[:& filters/filters {:shape (assoc shape :shadow []) :filter-id filter-id-blur}]]
;; This need to be separated in two layers so the clip doesn't affect the shadow filters
;; otherwise the shadow will be clipped and not visible
;; This need to be separated in two layers so the clip doesn't affect the shadow filters
;; otherwise the shadow will be clipped and not visible
[:g.frame-container-shadows {:filter filter-str-shadows}
[:g {:clip-path (when-not ^boolean show-content? (frame-clip-url shape render-id))
;; A frame sets back normal fill behavior (default
;; transparent). It may have been changed to default black
;; if a shape coming from an imported SVG file is
;; rendered. See main.ui.shapes.attrs/add-style-attrs.
;; A frame sets back normal fill behavior (default
;; transparent). It may have been changed to default black
;; if a shape coming from an imported SVG file is
;; rendered. See main.ui.shapes.attrs/add-style-attrs.
:fill "none"}
[:& shape-fills {:shape shape}

View File

@@ -447,7 +447,7 @@
(mf/use-effect
(mf/deps nav-scroll)
(fn []
;; Set scroll position after navigate
;; Set scroll position after navigate
(when (number? nav-scroll)
(let [viewer-section (dom/get-element "viewer-section")]
(st/emit! (dv/reset-nav-scroll))
@@ -481,8 +481,8 @@
:fit (st/emit! dv/zoom-to-fit)
:fill (st/emit! dv/zoom-to-fill)
nil)
;; Navigate animation needs to be started after navigation
;; is complete, and we have the next page index.
;; Navigate animation needs to be started after navigation
;; is complete, and we have the next page index.
(let [nav-animation (d/seek #(= (:kind %) :go-to-frame) (vals current-animations))]
(when nav-animation
(let [orig-viewport (mf/ref-val orig-viewport-ref)
@@ -498,7 +498,7 @@
(mf/use-effect
(mf/deps current-animations)
(fn []
;; Overlay animations may be started when needed.
;; Overlay animations may be started when needed.
(when current-animations
(doseq [[overlay-frame-id animation-vals] current-animations]
(let [overlay-viewport (dom/get-element (str "overlay-" (str (:overlay-id animation-vals))))

View File

@@ -47,7 +47,7 @@
(defn- ignore-frame-shape
[shape objects manual?]
(let [shape (cond-> shape ;; When the the interaction is not manual and its origin is a frame,
;; we need to ignore it on all the find-frame calculations
;; we need to ignore it on all the find-frame calculations
(and (:frame-id shape) (not manual?))
(assoc :type :rect))
objects (assoc objects (:id shape) shape)]

View File

@@ -112,7 +112,7 @@
{:class (stl/css :file-name)
:title file-name
:on-double-click start-editing-name}
;;-- Persistende state widget
;;-- Persistende state widget
[:div {:class (case persistence-status
:pending (stl/css :status-notification :pending-status)
:saving (stl/css :status-notification :saving-status)

View File

@@ -191,7 +191,7 @@
:key "frame-container"
:opacity (when ^boolean hidden? 0)}
;; When there is no thumbnail, we generate a empty rect.
;; When there is no thumbnail, we generate a empty rect.
(when (and (not ^boolean content-visible?) (not @imposter-loaded))
[:g.frame-placeholder
[:rect {:x x

View File

@@ -106,7 +106,7 @@
:title (if is-local
(mf/html [:div {:class (stl/css :special-title)}
(tr "workspace.assets.local-library")])
;; Do we need to add shared info here?
;; Do we need to add shared info here?
(mf/html [:div {:class (stl/css :special-title)}
file-name]))}
(when-not ^boolean is-local

View File

@@ -67,7 +67,7 @@
(let [data (into [] (remove nil?) (dwc/extract-all-colors shapes file-id libraries))
groups (d/group-by :attrs #(dissoc % :attrs) data)
;; Unique color attribute maps
;; Unique color attribute maps
all-colors (distinct (mapv :attrs data))
;; Split into: library colors, token colors, and plain colors

View File

@@ -281,7 +281,7 @@
:options stroke-style-options
:on-change on-style-change}]])]
;; Stroke Caps
;; Stroke Caps
(when show-caps
[:div {:class (stl/css :stroke-caps-options)}
[:& select {:default-value (:stroke-cap-start stroke)

View File

@@ -46,7 +46,7 @@
{::mf/private true}
[{:keys [tokens-lib selected-token-set-id]}]
(let [selected-token-set
(mf/with-memo [tokens-lib]
(mf/with-memo [tokens-lib selected-token-set-id]
(when selected-token-set-id
(some-> tokens-lib (ctob/get-set selected-token-set-id))))
@@ -62,18 +62,20 @@
[:div {:class (stl/css :sets-header-container)}
[:> text* {:as "span"
:typography "headline-small"
:class (stl/css :sets-header)}
:class (stl/css :sets-header)
:data-testid "active-token-set-title"}
(tr "workspace.tokens.tokens-section-title" (ctob/get-name selected-token-set))]
[:div {:class (stl/css :sets-header-status) :title (tr "workspace.tokens.inactive-set-description")}
(when (and (some? selected-token-set-id)
(not (token-set-active? (ctob/get-name selected-token-set))))
[:div {:class (stl/css :sets-header-status) :title (tr "workspace.tokens.inactive-set-description")}
;; NOTE: when no set in tokens-lib, the selected-token-set-id
;; will be `nil`, so for properly hide the inactive message we
;; check that at least `selected-token-set-id` has a value
(when (and (some? selected-token-set-id)
(not (token-set-active? (ctob/get-name selected-token-set))))
[:*
[:> icon* {:class (stl/css :sets-header-status-icon) :icon-id i/eye-off}]
[:> text* {:as "span" :typography "body-small" :class (stl/css :sets-header-status-text)}
(tr "workspace.tokens.inactive-set")]])]]))
(tr "workspace.tokens.inactive-set")]]])]))
(mf/defc tokens-section*
{::mf/private true}
@@ -158,7 +160,7 @@
[:& token-context-menu]
[:> token-node-context-menu* {:on-delete-node delete-node}]
[:& selected-set-info* {:tokens-lib tokens-lib
[:> selected-set-info* {:tokens-lib tokens-lib
:selected-token-set-id selected-token-set-id}]
(for [type filled-group]

View File

@@ -211,7 +211,7 @@
(let [;; StyleDictionary will always convert to hex/rgba, so we take the format from the value input field
prev-input-color (some-> value
(tinycolor/valid-color))
;; If the input is a reference we will take the format from the computed value
;; If the input is a reference we will take the format from the computed value
prev-computed-color (when-not prev-input-color
(some-> value (tinycolor/valid-color)))
prev-format (some-> (or prev-input-color prev-computed-color)
@@ -380,7 +380,7 @@
(let [;; StyleDictionary will always convert to hex/rgba, so we take the format from the value input field
prev-input-color (some-> value
(tinycolor/valid-color))
;; If the input is a reference we will take the format from the computed value
;; If the input is a reference we will take the format from the computed value
prev-computed-color (when-not prev-input-color
(some-> value (tinycolor/valid-color)))
prev-format (some-> (or prev-input-color prev-computed-color)

View File

@@ -28,7 +28,6 @@
[app.main.ui.forms :as fc]
[app.main.ui.workspace.tokens.management.forms.controls :as token.controls]
[app.main.ui.workspace.tokens.management.forms.validators :refer [default-validate-token]]
[app.main.ui.workspace.tokens.remapping-modal :as remapping-modal]
[app.util.dom :as dom]
[app.util.forms :as fm]
[app.util.i18n :refer [tr]]
@@ -182,9 +181,33 @@
(when (or (k/enter? e) (k/space? e))
(on-cancel e))))
on-remap-token
(mf/use-fn
(mf/deps token)
(fn [valid-token name old-name description]
(st/emit!
(dwtl/update-token (:id token)
{:name name
:value (:value valid-token)
:description description})
(remap/remap-tokens old-name name)
(dwtp/propagate-workspace-tokens)
(modal/hide!))))
on-rename-token
(mf/use-fn
(mf/deps token)
(fn [valid-token name description]
(st/emit!
(dwtl/update-token (:id token)
{:name name
:value (:value valid-token)
:description description})
(modal/hide!))))
on-submit
(mf/use-fn
(mf/deps validate-token token tokens token-type value-subfield type active-tab)
(mf/deps validate-token token tokens token-type value-subfield type active-tab on-remap-token on-rename-token is-create)
(fn [form _event]
(let [name (get-in @form [:clean-data :name])
path (str (d/name token-type) "." name)
@@ -202,22 +225,15 @@
file-data (dh/lookup-file-data state)
old-name (:name token)
is-rename (and (= action "edit") (not= name old-name))
references-count (remap/count-token-references file-data old-name)]
references-count (remap/count-token-references file-data old-name)
on-remap #(on-remap-token valid-token name old-name description)
on-rename #(on-rename-token valid-token name description)]
(if (and is-rename (> references-count 0))
(remapping-modal/show-remapping-modal
{:old-token-name old-name
:new-token-name name
:references-count references-count
:on-confirm (fn []
(st/emit!
(dwtl/update-token (:id token)
{:name name
:value (:value valid-token)
:description description})
(remap/remap-tokens old-name name)
(dwtp/propagate-workspace-tokens)
(modal/hide!)))
:on-cancel #(modal/hide!)})
(st/emit! (modal/show :tokens/remapping-confirmation {:old-token-name old-name
:new-token-name name
:references-count references-count
:on-remap on-remap
:on-rename on-rename}))
(st/emit!
(if is-create
(dwtl/create-token (ctob/make-token {:name name

View File

@@ -295,7 +295,8 @@
errors?
[:> icon*
{:icon-id i/broken-link
:class (stl/css :token-pill-icon)}]
:class (stl/css :token-pill-icon)
:aria-label (tr "workspace.tokens.missing-reference")}]
color
[:> swatch* {:background color

View File

@@ -146,7 +146,7 @@
:active-theme-tokens active-theme-tokens
:on-click on-token-pill-click
:on-context-menu on-pill-context-menu}])
;; Render segment folder
;; Render segment folder
[:ul {:class (stl/css :node-parent)
:key (:path node)}
[:> folder-node* {:node node

View File

@@ -11,22 +11,15 @@
[app.main.data.modal :as modal]
[app.main.store :as st]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.foundations.typography :as t]
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
[app.util.dom :as dom]
[app.main.ui.ds.foundations.typography.text :refer [text*]]
[app.util.i18n :refer [tr]]
[app.util.keyboard :as kbd]
[rumext.v2 :as mf]))
(defn show-remapping-modal
"Show the token remapping confirmation modal"
[{:keys [old-token-name new-token-name references-count on-confirm on-cancel]}]
(let [props {:old-token-name old-token-name
:new-token-name new-token-name
:references-count references-count
:on-confirm on-confirm
:on-cancel on-cancel}]
(st/emit! (modal/show :tokens/remapping-confirmation props))))
(defn hide-remapping-modal
"Hide the token remapping confirmation modal"
[]
@@ -34,73 +27,73 @@
;; Remapping Modal Component
(mf/defc token-remapping-modal
{::mf/wrap-props false
::mf/register modal/components
{::mf/register modal/components
::mf/register-as :tokens/remapping-confirmation}
[{:keys [old-token-name new-token-name references-count on-confirm on-cancel]}]
(let [remapping-in-progress* (mf/use-state false)
remapping-in-progress? (deref remapping-in-progress*)
[{:keys [old-token-name new-token-name on-remap on-rename]}]
(let [remap-modal (get @st/state :remap-modal)
;; Remap logic on confirm
on-confirm-remap
confirm-remap
(mf/use-fn
(mf/deps on-confirm remapping-in-progress*)
(fn [e]
(dom/prevent-default e)
(dom/stop-propagation e)
(reset! remapping-in-progress* true)
(mf/deps on-remap remap-modal)
(fn []
;; Call shared remapping logic
(let [state @st/state
remap-modal (:remap-modal state)
old-token-name (:old-token-name remap-modal)
(let [old-token-name (:old-token-name remap-modal)
new-token-name (:new-token-name remap-modal)]
(st/emit! [:tokens/remap-tokens old-token-name new-token-name]))
(when (fn? on-confirm)
(on-confirm))))
(when (fn? on-remap)
(on-remap))))
on-cancel-remap
rename-token
(mf/use-fn
(mf/deps on-cancel)
(fn [e]
(dom/prevent-default e)
(dom/stop-propagation e)
(modal/hide!)
(when (fn? on-cancel)
(on-cancel))))]
(mf/deps on-rename)
(fn []
(when (fn? on-rename)
(on-rename))))
cancel-action
(mf/use-fn
(fn []
(hide-remapping-modal)))
;; Close modal on Escape key if not in progress
on-key-down
(mf/use-fn
(mf/deps cancel-action)
(fn [event]
(when (kbd/enter? event)
(cancel-action))))]
[:div {:class (stl/css :modal-overlay)
:on-key-down on-key-down
:role "alertdialog"
:aria-modal "true"
:aria-labelledby "modal-title"}
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-dialog)
:data-testid "token-remapping-modal"}
[:> icon-button* {:on-click cancel-action
:class (stl/css :close-btn)
:icon i/close
:variant "action"
:aria-label (tr "labels.close")}]
[:div {:class (stl/css :modal-header)}
[:> heading* {:level 2
:typography "headline-medium"
:id "modal-title"
:typography "headline-large"
:class (stl/css :modal-title)}
(tr "workspace.tokens.remap-token-references")]]
(tr "workspace.tokens.remap-token-references-title" old-token-name new-token-name)]]
[:div {:class (stl/css :modal-content)}
[:> heading* {:level 3
:typography "title-medium"
:class (stl/css :modal-msg)}
(tr "workspace.tokens.renaming-token-from-to" old-token-name new-token-name)]
[:div {:class (stl/css :modal-scd-msg)}
(if (> references-count 0)
(tr "workspace.tokens.references-found" references-count)
(tr "workspace.tokens.no-references-found"))]
(when remapping-in-progress?
[:> context-notification*
{:level :info
:appearance :ghost}
(tr "workspace.tokens.remapping-in-progress")])]
[:> text* {:as "p" :typography t/body-medium} (tr "workspace.tokens.remap-warning-effects")]
[:> text* {:as "p" :typography t/body-medium} (tr "workspace.tokens.remap-warning-time")]]
[:div {:class (stl/css :modal-footer)}
[:div {:class (stl/css :action-buttons)}
[:> button* {:on-click on-cancel-remap
[:> button* {:on-click rename-token
:type "button"
:variant "secondary"
:disabled remapping-in-progress?}
(tr "labels.cancel")]
[:> button* {:on-click on-confirm-remap
:variant "secondary"}
(tr "workspace.tokens.not-remap")]
[:> button* {:on-click confirm-remap
:type "button"
:variant "primary"
:disabled remapping-in-progress?}
(if (> references-count 0)
(tr "workspace.tokens.remap-and-rename")
(tr "workspace.tokens.rename-only"))]]]]]))
:variant "primary"}
(tr "workspace.tokens.remap")]]]]]))

View File

@@ -10,6 +10,9 @@
@use "refactor/common-refactor.scss" as deprecated;
.modal-overlay {
--modal-title-foreground-color: var(--color-foreground-primary);
--modal-text-foreground-color: var(--color-foreground-secondary);
@extend .modal-overlay-base;
display: flex;
justify-content: center;
@@ -17,16 +20,22 @@
position: fixed;
inset-inline-start: 0;
inset-block-start: 0;
height: 100%;
width: 100%;
block-size: 100%;
inline-size: 100%;
background-color: var(--overlay-color);
}
.close-btn {
position: absolute;
inset-block-start: $sz-6;
inset-inline-end: $sz-6;
}
.modal-dialog {
@extend .modal-container-base;
width: 100%;
max-width: 32rem;
max-height: unset;
inline-size: 100%;
max-inline-size: 32rem;
max-block-size: unset;
user-select: none;
position: relative;
}
@@ -45,11 +54,7 @@
.modal-content {
@include t.use-typography("body-large");
margin-block-end: var(--sp-xxl);
padding: var(--sp-xxl) 0;
display: flex;
flex-direction: column;
gap: var(--sp-l);
color: var(--modal-text-foreground-color);
}
.modal-footer {

View File

@@ -245,7 +245,7 @@
(dom/child? (dom/get-target event) (dom/query ".grid-layout-editor"))
(dom/class? (dom/get-target event) "viewport-selrect"))
(let [position (dom/get-client-position event)]
;; Delayed callback because we need to wait to the previous context menu to be closed
;; Delayed callback because we need to wait to the previous context menu to be closed
(ts/schedule
#(st/emit!
(if (and (not read-only?) (some? @hover))

View File

@@ -123,7 +123,7 @@
:aria-label (tr "workspace.path.actions.draw-nodes" (sc/get-tooltip :draw-nodes))
:tooltip-placement "bottom"
:on-click on-select-draw-mode}]
;; Edit mode
;; Edit mode
[:> icon-button* {:variant "ghost"
:class (stl/css :topbar-btn)
:icon i/move

View File

@@ -200,7 +200,7 @@
is-variant? [:use {:href "#icon-component"}])])
(if ^boolean edition?
;; Case when edition? is true
;; Case when edition? is true
[:foreignObject {:x text-pos-x
:y -15
:width (max 0 (- text-width text-pos-x))
@@ -217,7 +217,7 @@
:ref ref
:default-value (:name frame)
:on-blur accept-edit}]]
;; Case when edition? is false
;; Case when edition? is false
[:foreignObject {:x text-pos-x
:y -11
:width (max 0 (- text-width text-pos-x))

View File

@@ -215,7 +215,7 @@
:meroitic #"\uD802[\uDD80-\uDD9F]"
;; Arrows, Mathematical Operators, Misc Technical, Geometric Shapes, Misc Symbols, Dingbats, Supplemental Arrows, etc.
:symbols #"[\u2190-\u21FF\u2200-\u22FF\u2300-\u23FF\u25A0-\u25FF\u2600-\u26FF\u2700-\u27BF\u2B00-\u2BFF]"
;; Additional arrows, math, technical, geometric, and symbol blocks
;; Additional arrows, math, technical, geometric, and symbol blocks
:symbols-2 #"[\u2190-\u21FF\u2200-\u22FF\u2300-\u23FF\u25A0-\u25FF\u2600-\u26FF\u2700-\u27BF\u2B00-\u2BFF]"
:music #"[\u2669-\u267B]|\uD834[\uDD00-\uDD1F]"})

View File

@@ -108,7 +108,7 @@ void main() {
(.bufferData ^js gl (.-ARRAY_BUFFER ^js gl) (js/Float32Array. positions) (.-STATIC_DRAW ^js gl))
(.enableVertexAttribArray ^js gl position-location)
(.vertexAttribPointer ^js gl position-location 2 (.-FLOAT ^js gl) false 0 0)
;; Set up texcoord buffer
;; Set up texcoord buffer
(.bindBuffer ^js gl (.-ARRAY_BUFFER ^js gl) texcoord-buffer)
(.bufferData ^js gl (.-ARRAY_BUFFER ^js gl) (js/Float32Array. texcoords) (.-STATIC_DRAW ^js gl))
(.enableVertexAttribArray ^js gl texcoord-location)

View File

@@ -258,8 +258,8 @@
(let [values (unchecked-get wasm/serializers "font-style")
default (unchecked-get values "normal")]
(case font-style
;; NOTE: normal == regular!
;; is it OK to keep those two values in our cljs model?
;; NOTE: normal == regular!
;; is it OK to keep those two values in our cljs model?
"normal" (unchecked-get values "normal")
"regular" (unchecked-get values "normal")
"italic" (unchecked-get values "italic")

View File

@@ -7,5 +7,5 @@
[hex opacity]
(let [rgb (js/parseInt (subs hex 1) 16)
a (mth/floor (* (or opacity 1) 0xff))]
;; rgba >>> 0 so we have an unsigned representation
;; rgba >>> 0 so we have an unsigned representation
(unsigned-bit-shift-right (bit-or (bit-shift-left a 24) rgb) 0)))

View File

@@ -56,7 +56,7 @@
is-input?
(= (dom/get-tag-name target) "INPUT")]
;; ignore when pasting into an editable control
;; ignore when pasting into an editable control
(if-not (or content-editable? is-input?)
(-> event
(dom/event->browser-event)

View File

@@ -44,9 +44,9 @@
(js/console.log
label
"[" (:name data) "]"
;; (if currentTarget
;; (str "<" (.-localName currentTarget) " " (.-textContent currentTarget) ">")
;; "null")
;; (if currentTarget
;; (str "<" (.-localName currentTarget) " " (.-textContent currentTarget) ">")
;; "null")
(if relatedTarget
(str "<" (.-localName relatedTarget) " " (.-textContent relatedTarget) ">")
"null"))))

View File

@@ -54,8 +54,8 @@
(assoc acc key (get node key default-value)))) {} attrs)
fills
(cond
;; DEPRECATED: still here for backward compatibility with
;; old penpot files that still has a single color.
;; DEPRECATED: still here for backward compatibility with
;; old penpot files that still has a single color.
(or (some? (:fill-color node))
(some? (:fill-opacity node))
(some? (:fill-color-gradient node)))

View File

@@ -100,7 +100,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
blue1 (cths/get-shape file :blue1)
features #{"components/v2"}
@@ -121,12 +121,12 @@
blue1' (cths/get-shape file' :blue1)
copied-blue1' (find-copied-shape blue1' page' uuid/zero)]
;; ==== Check
;; ==== Check
;; blue1 has swap-id
;; blue1 has swap-id
(t/is (some? (ctk/get-swap-slot blue1')))
;; copied-blue1 has not swap-id
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -138,7 +138,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
b1 (cths/get-shape file :frame-b1)
blue1 (cths/get-shape file :blue1)
@@ -161,11 +161,11 @@
blue1' (cths/get-shape file' :blue1)
copied-blue1' (find-copied-shape blue1' page' (:id b1'))]
;; ==== Check
;; blue1 has swap-id
;; ==== Check
;; blue1 has swap-id
(t/is (some? (ctk/get-swap-slot blue1')))
;; copied-blue1 has not swap-id
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -176,7 +176,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
yellow (cths/get-shape file :frame-yellow)
blue1 (cths/get-shape file :blue1)
@@ -199,11 +199,11 @@
blue1' (cths/get-shape file' :blue1)
copied-blue1' (find-copied-shape blue1' page' (:id yellow'))]
;; ==== Check
;; blue1 has swap-id
;; ==== Check
;; blue1 has swap-id
(t/is (some? (ctk/get-swap-slot blue1')))
;; copied-blue1 has not swap-id
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -215,7 +215,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
b2 (cths/get-shape file :frame-b2)
blue1 (cths/get-shape file :blue1)
@@ -238,11 +238,11 @@
blue1' (cths/get-shape file' :blue1)
copied-blue1' (find-copied-shape blue1' page' (:id b2'))]
;; ==== Check
;; blue1 has swap-id
;; ==== Check
;; blue1 has swap-id
(t/is (some? (ctk/get-swap-slot blue1')))
;; copied-blue1 has not swap-id
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -254,7 +254,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
blue1 (cths/get-shape file :blue1)
features #{"components/v2"}
@@ -276,8 +276,8 @@
page' (cthf/current-page file')
copied-blue1' (find-copied-shape blue1 page' uuid/zero)]
;; ==== Check
;; copied-blue1 has not swap-id
;; ==== Check
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -290,7 +290,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
yellow (cths/get-shape file :frame-yellow)
blue1 (cths/get-shape file :blue1)
@@ -314,8 +314,8 @@
yellow' (cths/get-shape file' :frame-yellow)
copied-blue1' (find-copied-shape blue1 page' (:id yellow'))]
;; ==== Check
;; copied-blue1 has not swap-id
;; ==== Check
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -327,7 +327,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
b2 (cths/get-shape file :frame-b2)
blue1 (cths/get-shape file :blue1)
@@ -351,8 +351,8 @@
b2' (cths/get-shape file' :frame-b2)
copied-blue1' (find-copied-shape blue1 page' (:id b2'))]
;; ==== Check
;; copied-blue1 has not swap-id
;; ==== Check
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -364,7 +364,7 @@
file (setup-file-blue1-in-yellow)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
yellow (cths/get-shape file :frame-yellow)
features #{"components/v2"}
@@ -386,12 +386,12 @@
blue1' (cths/get-shape file' :blue1)
copied-blue1' (find-copied-shape blue1' page' (:id copied-yellow'))]
;; ==== Check
;; ==== Check
;; blue1 has swap-id
;; blue1 has swap-id
(t/is (some? (ctk/get-swap-slot blue1')))
;; copied-blue1 has not swap-id
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -402,7 +402,7 @@
file (setup-file-blue1-in-yellow)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
yellow (cths/get-shape file :frame-yellow)
b1 (cths/get-shape file :frame-b1)
@@ -426,12 +426,12 @@
blue1' (cths/get-shape file' :blue1)
copied-blue1' (find-copied-shape blue1' page' (:id copied-yellow'))]
;; ==== Check
;; ==== Check
;; blue1 has swap-id
;; blue1 has swap-id
(t/is (some? (ctk/get-swap-slot blue1')))
;; copied-blue1 has not swap-id
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -442,7 +442,7 @@
file (setup-file-blue1-in-yellow)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
yellow (cths/get-shape file :frame-yellow)
b2 (cths/get-shape file :frame-b2)
@@ -466,11 +466,11 @@
blue1' (cths/get-shape file' :blue1)
copied-blue1' (find-copied-shape blue1' page' (:id copied-yellow'))]
;; ==== Check
;; ==== Check
;; blue1 has swap-id
;; blue1 has swap-id
(t/is (some? (ctk/get-swap-slot blue1')))
;; copied-blue1 has not swap-id
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -482,7 +482,7 @@
store (ths/setup-store file)
blue1 (cths/get-shape file :blue1)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
yellow (cths/get-shape file :frame-yellow)
features #{"components/v2"}
@@ -505,8 +505,8 @@
copied-yellow' (find-copied-shape yellow page' uuid/zero)
copied-blue1' (find-copied-shape blue1 page' (:id copied-yellow'))]
;; ==== Check
;; copied-blue1 has not swap-id
;; ==== Check
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -518,7 +518,7 @@
store (ths/setup-store file)
blue1 (cths/get-shape file :blue1)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
yellow (cths/get-shape file :frame-yellow)
b1 (cths/get-shape file :frame-b1)
@@ -543,8 +543,8 @@
copied-yellow' (find-copied-shape yellow page' (:id b1'))
copied-blue1' (find-copied-shape blue1 page' (:id copied-yellow'))]
;; ==== Check
;; copied-blue1 has not swap-id
;; ==== Check
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -556,7 +556,7 @@
store (ths/setup-store file)
blue1 (cths/get-shape file :blue1)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
yellow (cths/get-shape file :frame-yellow)
b2 (cths/get-shape file :frame-b2)
@@ -581,8 +581,8 @@
copied-yellow' (find-copied-shape yellow page' (:id b2'))
copied-blue1' (find-copied-shape blue1 page' (:id copied-yellow'))]
;; ==== Check
;; copied-blue1 has not swap-id
;; ==== Check
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))
@@ -593,7 +593,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
green (cths/get-shape file :green-copy)
features #{"components/v2"}
@@ -615,12 +615,12 @@
copied-green' (find-copied-shape green page' uuid/zero)
copied-blue2' (find-copied-shape blue2' page' (:id copied-green'))]
;; ==== Check
;; ==== Check
;; blue2 has swap-id
;; blue2 has swap-id
(t/is (some? (ctk/get-swap-slot blue2')))
;; copied-blue2 also has swap-id
;; copied-blue2 also has swap-id
(t/is (some? copied-blue2'))
(t/is (some? (ctk/get-swap-slot copied-blue2')))))))))
@@ -631,7 +631,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
b1 (cths/get-shape file :frame-b1)
green (cths/get-shape file :green-copy)
@@ -655,12 +655,12 @@
copied-green' (find-copied-shape green page' (:id b1'))
copied-blue2' (find-copied-shape blue2' page' (:id copied-green'))]
;; ==== Check
;; ==== Check
;; blue1 has swap-id
;; blue1 has swap-id
(t/is (some? (ctk/get-swap-slot blue2')))
;; copied-blue1 also has swap-id
;; copied-blue1 also has swap-id
(t/is (some? copied-blue2'))
(t/is (some? (ctk/get-swap-slot copied-blue2')))))))))
@@ -671,7 +671,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
b2 (cths/get-shape file :frame-b2)
green (cths/get-shape file :green-copy)
@@ -695,12 +695,12 @@
copied-green' (find-copied-shape green page' (:id b2'))
copied-blue2' (find-copied-shape blue2' page' (:id copied-green'))]
;; ==== Check
;; ==== Check
;; blue2 has swap-id
;; blue2 has swap-id
(t/is (some? (ctk/get-swap-slot blue2')))
;; copied-blue1 also has swap-id
;; copied-blue1 also has swap-id
(t/is (some? copied-blue2'))
(t/is (some? (ctk/get-swap-slot copied-blue2')))))))))
@@ -712,7 +712,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
green (cths/get-shape file :green-copy)
blue2 (cths/get-shape file :blue-copy-in-green-copy)
@@ -736,8 +736,8 @@
copied-green' (find-copied-shape green page' uuid/zero)
copied-blue1' (find-copied-shape blue2 page' (:id copied-green'))]
;; ==== Check
;; copied-blue1 has swap-id
;; ==== Check
;; copied-blue1 has swap-id
(t/is (some? copied-blue1'))
(t/is (some? (ctk/get-swap-slot copied-blue1')))))))))
@@ -748,7 +748,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
b1 (cths/get-shape file :frame-b1)
green (cths/get-shape file :green-copy)
@@ -774,8 +774,8 @@
copied-green' (find-copied-shape green page' (:id b1'))
copied-blue2' (find-copied-shape blue2 page' (:id copied-green'))]
;; ==== Check
;; copied-blue1 has swap-id
;; ==== Check
;; copied-blue1 has swap-id
(t/is (some? copied-blue2'))
(t/is (some? (ctk/get-swap-slot copied-blue2')))))))))
@@ -786,7 +786,7 @@
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
b2 (cths/get-shape file :frame-b2)
blue2 (cths/get-shape file :blue-copy-in-green-copy)
@@ -812,8 +812,8 @@
copied-green' (find-copied-shape green page' (:id b2'))
copied-blue2' (find-copied-shape blue2 page' (:id copied-green'))]
;; ==== Check
;; copied-blue1 has swap-id
;; ==== Check
;; copied-blue1 has swap-id
(t/is (some? copied-blue2'))
(t/is (some? (ctk/get-swap-slot copied-blue2')))))))))
@@ -823,10 +823,10 @@
(t/async
done
(let [;; ==== Setup
;; {:frame-red} [:name frame-blue] # [Component :red]
;; {:frame-blue} [:name frame-blue] #[Component :blue]
;; {:frame-green} [:name frame-green] #[Component :green]
;; :blue1 [:name frame-blue, :swap-slot-label :red-copy-green] @--> frame-blue
;; {:frame-red} [:name frame-blue] # [Component :red]
;; {:frame-blue} [:name frame-blue] #[Component :blue]
;; {:frame-green} [:name frame-green] #[Component :green]
;; :blue1 [:name frame-blue, :swap-slot-label :red-copy-green] @--> frame-blue
file (-> (cthf/sample-file :file1)
(ctho/add-frame :frame-red :name "frame-blue")
@@ -839,7 +839,7 @@
(cthc/component-swap :red-copy-green :blue :blue1))
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
green (cths/get-shape file :frame-green)
features #{"components/v2"}
@@ -862,11 +862,11 @@
copied-green' (find-copied-shape green' page' uuid/zero)
copied-blue1' (find-copied-shape blue1' page' (:id copied-green'))]
;; ==== Check
;; blue1 has swap-id
;; ==== Check
;; blue1 has swap-id
(t/is (some? (ctk/get-swap-slot blue1')))
;; copied-blue1 has not swap-id
;; copied-blue1 has not swap-id
(t/is (some? copied-blue1'))
(t/is (nil? (ctk/get-swap-slot copied-blue1')))))))))

View File

@@ -74,7 +74,7 @@
file (setup-base-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
events
[(dws/select-shape (cthi/id :frame1))
(dwl/add-component)]]
@@ -87,7 +87,7 @@
frame1' (cths/get-shape file' :frame1)
tokens-frame1' (:applied-tokens frame1')]
;; ==== Check
;; ==== Check
(t/is (= (count tokens-frame1') 4))
(t/is (= (get tokens-frame1' :r1) "test-token-1"))
(t/is (= (get tokens-frame1' :r2) "test-token-1"))
@@ -105,7 +105,7 @@
file (setup-file-with-main)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
events
[(dwl/instantiate-component (:id file)
(cthi/id :component1)
@@ -119,7 +119,7 @@
c-frame1' (dsh/lookup-shape new-state (first selected))
tokens-frame1' (:applied-tokens c-frame1')]
;; ==== Check
;; ==== Check
(t/is (= (count tokens-frame1') 4))
(t/is (= (get tokens-frame1' :r1) "test-token-1"))
(t/is (= (get tokens-frame1' :r2) "test-token-1"))
@@ -137,7 +137,7 @@
file (setup-file-with-copy)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
events [(dwta/apply-token {:shape-ids [(cthi/id :frame1)]
:attributes #{:r1 :r2 :r3 :r4}
:token (toht/get-token file "test-token-2")
@@ -153,7 +153,7 @@
c-frame1' (cths/get-shape file' :c-frame1)
tokens-frame1' (:applied-tokens c-frame1')]
;; ==== Check
;; ==== Check
(t/is (= (count tokens-frame1') 4))
(t/is (= (get tokens-frame1' :r1) "test-token-2"))
(t/is (= (get tokens-frame1' :r2) "test-token-2"))
@@ -174,7 +174,7 @@
file (setup-file-with-copy)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
events [(dwta/unapply-token {:shape-ids [(cthi/id :frame1)]
:attributes #{:r1 :r2 :r3 :r4}
:token (toht/get-token file "test-token-1")})]
@@ -189,7 +189,7 @@
c-frame1' (cths/get-shape file' :c-frame1)
tokens-frame1' (:applied-tokens c-frame1')]
;; ==== Check
;; ==== Check
(t/is (= (count tokens-frame1') 0))
(t/is (= (get c-frame1' :r1) 25))
(t/is (= (get c-frame1' :r2) 25))
@@ -245,7 +245,7 @@
file (setup-file-with-copy)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
events [(dwta/apply-token {:shape-ids [(cthi/id :c-frame1)]
:attributes #{:r1 :r2 :r3 :r4}
:token (toht/get-token file "test-token-2")
@@ -265,7 +265,7 @@
c-frame1' (cths/get-shape file' :c-frame1)
tokens-frame1' (:applied-tokens c-frame1')]
;; ==== Check
;; ==== Check
(t/is (= (count tokens-frame1') 4))
(t/is (= (get tokens-frame1' :r1) "test-token-2"))
(t/is (= (get tokens-frame1' :r2) "test-token-2"))
@@ -286,7 +286,7 @@
file (setup-file-with-copy)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
events [(dwta/unapply-token {:shape-ids [(cthi/id :c-frame1)]
:attributes #{:r1 :r2 :r3 :r4}
:token (toht/get-token file "test-token-1")})
@@ -305,7 +305,7 @@
c-frame1' (cths/get-shape file' :c-frame1)
tokens-frame1' (:applied-tokens c-frame1')]
;; ==== Check
;; ==== Check
(t/is (= (count tokens-frame1') 0))
(t/is (= (get c-frame1' :r1) 25))
(t/is (= (get c-frame1' :r2) 25))
@@ -369,7 +369,7 @@
(cthc/instantiate-component :component1 :c-frame1))
store (ths/setup-store file)
;; ==== Action
;; ==== Action
events [(dwtl/set-selected-token-set-id (cthi/id :test-token-set))
(dwtl/update-token (cthi/id :token-radius)
{:name "token-radius"

View File

@@ -162,17 +162,17 @@
(let [;; ==== Setup
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
;; For each main and first level copy:
;; - Duplicate it two times with copy-paste.
;; For each main and first level copy:
;; - Duplicate it two times with copy-paste.
events
(concat
(duplicate-each-main-and-first-level-copy file)
;; - Change color of Simple1
;; - Change color of Simple1
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
;; - Change color of the nearest main and check propagation to duplicated.
;; - Change color of the nearest main and check propagation to duplicated.
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"}))]
(ths/run-store
@@ -188,17 +188,17 @@
(let [;; ==== Setup
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
;; For each main and first level copy:
;; - Duplicate it two times with copy-paste.
;; For each main and first level copy:
;; - Duplicate it two times with copy-paste.
events
(concat
(duplicate-each-main-and-first-level-copy file)
;; - Change color of Simple1
;; - Change color of Simple1
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
;; - Change color of the nearest main and check propagation to duplicated.
;; - Change color of the nearest main and check propagation to duplicated.
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"})
(set-color-bottom-shape :frame-composed-2 file {:color "#333333"}))]
@@ -216,17 +216,17 @@
(let [;; ==== Setup
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; ==== Action
;; For each main and first level copy:
;; - Duplicate it two times with copy-paste.
;; For each main and first level copy:
;; - Duplicate it two times with copy-paste.
events
(concat
(duplicate-each-main-and-first-level-copy file)
;; - Change color of Simple1
;; - Change color of Simple1
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
;; - Change color of the nearest main and check propagation to duplicated.
;; - Change color of the nearest main and check propagation to duplicated.
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"})
(set-color-bottom-shape :frame-composed-2 file {:color "#333333"})
(set-color-bottom-shape :frame-composed-3 file {:color "#444444"}))]
@@ -251,14 +251,14 @@
events
(concat
(duplicate-simple-nested-in-main-and-group file)
;; - Change color of Simple1
;; - Change color of Simple1
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"}))]
(ths/run-store
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)]
;; Check propagation to all copies.
;; Check propagation to all copies.
(t/is (= (count-shapes file' "rect-simple-1" "#111111") 28)))))))))
(t/deftest copy-nested-in-main-2
@@ -275,14 +275,14 @@
events
(concat
(duplicate-simple-nested-in-main-and-group file)
;; - Change color of the nearest main
;; - Change color of the nearest main
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"}))]
(ths/run-store
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)]
;; Check propagation to duplicated.
;; Check propagation to duplicated.
(t/is (= (count-shapes file' "rect-simple-1" "#222222") 9)))))))))
(t/deftest copy-nested-in-main-3
@@ -299,14 +299,14 @@
events
(concat
(duplicate-simple-nested-in-main-and-group file)
;; - Change color of the copy you duplicated from.
;; - Change color of the copy you duplicated from.
(set-color-bottom-shape :group-3 file {:color "#333333"}))]
(ths/run-store
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)]
;; Check that it's NOT PROPAGATED.
;; Check that it's NOT PROPAGATED.
(t/is (= (count-shapes file' "rect-simple-1" "#333333") 2)))))))))
(t/deftest copy-nested-1
@@ -324,14 +324,14 @@
events
(concat
(duplicate-copy-nested-and-group-out-of-the-main file)
;; - Change color of Simple1
;; - Change color of Simple1
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"}))]
(ths/run-store
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)]
;; Check propagation to all copies.
;; Check propagation to all copies.
(t/is (= (count-shapes file' "rect-simple-1" "#111111") 20)))))))))
@@ -350,9 +350,9 @@
events
(concat
(duplicate-copy-nested-and-group-out-of-the-main file)
;; - Change color of Simple1
;; - Change color of Simple1
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
;; - Change color of the previous main
;; - Change color of the previous main
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"})
(set-color-bottom-shape :group-3 file {:color "#333333"}))]
@@ -360,7 +360,7 @@
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)]
;; Check that it's NOT PROPAGATED.
;; Check that it's NOT PROPAGATED.
(t/is (= (count-shapes file' "rect-simple-1" "#111111") 11))
(t/is (= (count-shapes file' "rect-simple-1" "#222222") 7))
(t/is (= (count-shapes file' "rect-simple-1" "#333333") 2)))))))))
@@ -380,9 +380,9 @@
events
(concat
(duplicate-copy-nested-and-group-out-of-the-main file :target-page-label :page-2)
;; - Change color of Simple1
;; - Change color of Simple1
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
;; - Change color of the previous main
;; - Change color of the previous main
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"})
(set-color-bottom-shape :group-3 file {:color "#333333"}))]
@@ -391,7 +391,7 @@
(fn [new-state]
(let [file' (-> (ths/get-file-from-state new-state)
(cthf/switch-to-page :page-2))]
;; Check that it's NOT PROPAGATED.
;; Check that it's NOT PROPAGATED.
(t/is (= (count-shapes file' "rect-simple-1" "#111111") 10))
(t/is (= (count-shapes file' "rect-simple-1" "#222222") 4))
(t/is (= (count-shapes file' "rect-simple-1" "#333333") 0)))))))))

View File

@@ -33,7 +33,7 @@
:id (uuid/next)
:position 0}
;; ==== Action
;; ==== Action
events
[(dw/update-guides guide)
(dw/update-position (:id frame1) {:x 100})]]

View File

@@ -76,7 +76,7 @@
(ctho/add-frame :frame-blue {:name "frame-blue"}))
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
frame-red (cths/get-shape file :frame-red)
frame-blue (cths/get-shape file :frame-blue)
@@ -99,7 +99,7 @@
frame-blue' (cths/get-shape file' :frame-blue)
copied-blue1' (find-copied-shape frame-blue' page' (:id frame-red'))]
;; ==== Check
;; ==== Check
(t/is (= (:parent-id copied-blue1') (:id frame-red')))))))))
(t/deftest copy-shape-to-component
@@ -113,7 +113,7 @@
(ctho/add-frame :frame-blue {:name "frame-blue"}))
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
frame-red (cths/get-shape file :frame-red)
frame-blue (cths/get-shape file :frame-blue)
@@ -136,7 +136,7 @@
frame-blue' (cths/get-shape file' :frame-blue)
copied-blue1' (find-copied-shape frame-blue' page' (:id frame-red'))]
;; ==== Check
;; ==== Check
(t/is (= (:parent-id copied-blue1') (:id frame-red')))))))))
(t/deftest copy-component-to-frame
@@ -150,7 +150,7 @@
(cthc/make-component :blue :frame-blue))
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
frame-red (cths/get-shape file :frame-red)
frame-blue (cths/get-shape file :frame-blue)
@@ -173,7 +173,7 @@
frame-blue' (cths/get-shape file' :frame-blue)
copied-blue1' (find-copied-shape frame-blue' page' (:id frame-red'))]
;; ==== Check
;; ==== Check
(t/is (= (:parent-id copied-blue1') (:id frame-red')))))))))
(t/deftest copy-component-to-component
@@ -188,7 +188,7 @@
(cthc/make-component :blue :frame-blue))
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
frame-red (cths/get-shape file :frame-red)
frame-blue (cths/get-shape file :frame-blue)
@@ -211,7 +211,7 @@
frame-blue' (cths/get-shape file' :frame-blue)
copied-blue1' (find-copied-shape frame-blue' page' (:id frame-red'))]
;; ==== Check
;; ==== Check
(t/is (not (ctk/main-instance? copied-blue1')))
(t/is (= (:parent-id copied-blue1') (:id frame-red')))))))))
@@ -228,7 +228,7 @@
(cthc/make-component :blue :frame-blue))
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
frame-red (cths/get-shape file :frame-red)
frame-blue (cths/get-shape file :frame-blue)
@@ -254,7 +254,7 @@
copied-blue1' (find-copied-shape frame-blue' page' (:id frame-red'))
copied-blue2' (find-copied-shape frame-blue' page' uuid/zero true)]
;; ==== Check
;; ==== Check
(t/is (nil? copied-blue1'))
(t/is (ctk/main-instance? copied-blue2'))
(t/is (= (:parent-id copied-blue2') uuid/zero))))))))
@@ -272,7 +272,7 @@
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
root (cths/get-shape-by-id file uuid/zero)
frame-red (cths/get-shape file :frame-red)
@@ -297,13 +297,13 @@
root-children' (->> (:shapes root')
(map #(cths/get-shape-by-id file' %)))]
;; ==== Check
;; The main shape of the component have no children
;; ==== Check
;; The main shape of the component have no children
(t/is (= 0 (count (:shapes frame-red'))))
;; Root had two children, now have 3
;; Root had two children, now have 3
(t/is (= 2 (count (:shapes root))))
(t/is (= 3 (count (:shapes root'))))
;; Two of the children of root are variant-containers
;; Two of the children of root are variant-containers
(t/is (= 2 (count (filter ctk/is-variant-container? root-children'))))))))))
(t/deftest cut-paste-variant-container-into-component
@@ -319,7 +319,7 @@
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
root (cths/get-shape-by-id file uuid/zero)
frame-red (cths/get-shape file :frame-red)
@@ -346,13 +346,13 @@
root-children' (->> (:shapes root')
(map #(cths/get-shape-by-id file' %)))]
;; ==== Check
;; The main shape of the component have no children
;; ==== Check
;; The main shape of the component have no children
(t/is (= 0 (count (:shapes frame-red'))))
;; Root had two children, now it still have two (because we have cutted one of them, and then created a new one)
;; Root had two children, now it still have two (because we have cutted one of them, and then created a new one)
(t/is (= 2 (count (:shapes root))))
(t/is (= 2 (count (:shapes root'))))
;; One of the children of root is a variant-container
;; One of the children of root is a variant-container
(t/is (= 1 (count (filter ctk/is-variant-container? root-children'))))))))))
(t/deftest copy-variant-into-different-variant-container
@@ -366,7 +366,7 @@
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
m01 (cths/get-shape file :m01)
v02 (cths/get-shape file :v02)
@@ -389,12 +389,12 @@
v02' (cths/get-shape file' :v02)
components' (cthc/get-components file')]
;; ==== Check
;; v02 had two children, now it have 3
;; ==== Check
;; v02 had two children, now it have 3
(t/is (= 2 (count (:shapes v02))))
(t/is (= 3 (count (:shapes v02'))))
;;There was 4 components, now there are 5
;;There was 4 components, now there are 5
(t/is (= 4 (count components)))
(t/is (= 5 (count components')))))))))
@@ -409,7 +409,7 @@
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
m01 (cths/get-shape file :m01)
v02 (cths/get-shape file :v02)
@@ -434,16 +434,16 @@
m03' (cths/get-shape file' :m03)
components' (cthc/get-components file')]
;; ==== Check
;; v02 had two children, now it have still 2
;; ==== Check
;; v02 had two children, now it have still 2
(t/is (= 2 (count (:shapes v02))))
(t/is (= 2 (count (:shapes v02'))))
;; m03 had no children, now it have 1
;; m03 had no children, now it have 1
(t/is (= 0 (count (:shapes m03))))
(t/is (= 1 (count (:shapes m03'))))
;;There was 4 components, now there is still 4
;;There was 4 components, now there is still 4
(t/is (= 4 (count components)))
(t/is (= 4 (count components')))))))))
@@ -457,7 +457,7 @@
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
m01 (cths/get-shape file :m01)
v01 (cths/get-shape file :v01)
@@ -480,12 +480,12 @@
v01' (cths/get-shape file' :v01)
components' (cthc/get-components file')]
;; ==== Check
;; v01 had two children, now it have 3
;; ==== Check
;; v01 had two children, now it have 3
(t/is (= 2 (count (:shapes v01))))
(t/is (= 3 (count (:shapes v01'))))
;;There was 2 components, now there are 3
;;There was 2 components, now there are 3
(t/is (= 2 (count components)))
(t/is (= 3 (count components')))))))))
@@ -499,7 +499,7 @@
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
m01 (cths/get-shape file :m01)
v01 (cths/get-shape file :v01)
@@ -522,12 +522,12 @@
v01' (cths/get-shape file' :v01)
components' (cthc/get-components file')]
;; ==== Check
;; v01 had two children, now it have 3
;; ==== Check
;; v01 had two children, now it have 3
(t/is (= 2 (count (:shapes v01))))
(t/is (= 3 (count (:shapes v01'))))
;;There was 2 components, now there are 3
;;There was 2 components, now there are 3
(t/is (= 2 (count components)))
(t/is (= 3 (count components')))))))))
@@ -541,7 +541,7 @@
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
m01 (cths/get-shape file :m01)
m02 (cths/get-shape file :m02)
@@ -565,12 +565,12 @@
v01' (cths/get-shape file' :v01)
components' (cthc/get-components file')]
;; ==== Check
;; v01 had two children, now it have 3
;; ==== Check
;; v01 had two children, now it have 3
(t/is (= 2 (count (:shapes v01))))
(t/is (= 3 (count (:shapes v01'))))
;;There was 2 components, now there are 3
;;There was 2 components, now there are 3
(t/is (= 2 (count components)))
(t/is (= 3 (count components')))))))))
@@ -586,7 +586,7 @@
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
frame-red (cths/get-shape file :frame-red)
v01 (cths/get-shape file :v01)
@@ -609,12 +609,12 @@
v01' (cths/get-shape file' :v01)
components' (cthc/get-components file')]
;; ==== Check
;; v01 had two children, now it have 3
;; ==== Check
;; v01 had two children, now it have 3
(t/is (= 2 (count (:shapes v01))))
(t/is (= 3 (count (:shapes v01'))))
;;There was 3 components, now there are 4
;;There was 3 components, now there are 4
(t/is (= 3 (count components)))
(t/is (= 4 (count components')))))))))
@@ -630,7 +630,7 @@
store (ths/setup-store file)
;; ==== Action
;; ==== Action
page (cthf/current-page file)
frame-red (cths/get-shape file :frame-red)
v01 (cths/get-shape file :v01)
@@ -655,15 +655,15 @@
m01' (cths/get-shape file' :m01)
components' (cthc/get-components file')]
;; ==== Check
;; v01 had two children, now it have still 2
;; ==== Check
;; v01 had two children, now it have still 2
(t/is (= 2 (count (:shapes v01))))
(t/is (= 2 (count (:shapes v01'))))
;; m01 had no children, now it have 1
;; m01 had no children, now it have 1
(t/is (= 0 (count (:shapes m01))))
(t/is (= 1 (count (:shapes m01'))))
;;There was 3 components, now there are still 3
;;There was 3 components, now there are still 3
(t/is (= 3 (count components)))
(t/is (= 3 (count components')))))))))

View File

@@ -118,9 +118,9 @@
:token (toht/get-token file "borderRadius.sm")
:shape-ids [(:id rect-1)]
:on-update-shape dwta/update-shape-radius-all})
;; Apply single `:r1` attribute to same shape
;; while removing other attributes from the border-radius set
;; but keep `:r4` for testing purposes
;; Apply single `:r1` attribute to same shape
;; while removing other attributes from the border-radius set
;; but keep `:r4` for testing purposes
(dwta/apply-token {:attributes #{:r1 :r2 :r3}
:token (toht/get-token file "borderRadius.md")
:shape-ids [(:id rect-1)]

View File

@@ -433,6 +433,9 @@ msgstr "(copy)"
msgid "dashboard.create-new-team"
msgstr "Create new team"
msgid "dashboard.create-new-org"
msgstr "Create new org"
#: src/app/main/ui/workspace/main_menu.cljs:661
msgid "dashboard.create-version-menu"
msgstr "Pin this version"
@@ -8069,6 +8072,10 @@ msgid "workspace.tokens.missing-references"
msgstr "Missing token references: "
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:123
msgid "workspace.tokens.missing-reference"
msgstr "Missing reference"
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:299
msgid "workspace.tokens.more-options"
msgstr "Right click to see options"
@@ -8159,36 +8166,29 @@ msgstr "Enter a token shadow alias"
msgid "workspace.tokens.reference-error"
msgstr "Reference Errors: "
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs:86
msgid "workspace.tokens.references-found"
msgstr "%s references found in your design"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs:105
msgid "workspace.tokens.remap-and-rename"
msgstr "Remap & Rename"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
#, unused
msgid "workspace.tokens.remap-explanation"
msgstr ""
"All references to this token will be automatically updated to use the new "
"name."
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs:78
msgid "workspace.tokens.remap-token-references-title"
msgstr "Remap all tokens that use `%s` to `%s`?"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs:78
msgid "workspace.tokens.remap-token-references"
msgstr "Remap Token References"
msgid "workspace.tokens.remap-warning-effects"
msgstr "This will change all layers and references that use the old token name."
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs:78
msgid "workspace.tokens.remap-warning-time"
msgstr "This action could take a while."
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs:92
msgid "workspace.tokens.remapping-in-progress"
msgstr "Remapping token references..."
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs:106
msgid "workspace.tokens.rename-only"
msgstr "Rename"
msgid "workspace.tokens.not-remap"
msgstr "Don't remap"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs:83
msgid "workspace.tokens.renaming-token-from-to"
msgstr "Renaming token from '%s' to '%s'"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs:105
msgid "workspace.tokens.remap"
msgstr "Remap tokens"
#: src/app/main/data/workspace/tokens/warnings.cljs:15, src/app/main/data/workspace/tokens/warnings.cljs:19, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:56, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:84, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:103, src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs:271, src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs:445, src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs:169, src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs:304, src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs:225, src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs:332, src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs:432, src/app/main/ui/workspace/tokens/management/token_pill.cljs:121
#, fuzzy

View File

@@ -442,6 +442,9 @@ msgstr "(copia)"
msgid "dashboard.create-new-team"
msgstr "Crear nuevo equipo"
msgid "dashboard.create-new-org"
msgstr "Crear nueva organización"
#: src/app/main/ui/workspace/main_menu.cljs:661
msgid "dashboard.create-version-menu"
msgstr "Guardar esta versión"
@@ -7991,6 +7994,10 @@ msgstr "Line height (multiplicador, px o %) o {alias}"
msgid "workspace.tokens.missing-references"
msgstr "Referencias de tokens no encontradas: "
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:123
msgid "workspace.tokens.missing-reference"
msgstr "Referencia no encontrada"
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:123
msgid "workspace.tokens.more-options"
msgstr "Click derecho para ver opciones"
@@ -8060,36 +8067,29 @@ msgstr "La referencia no es válida o no se encuentra en ningún set activo."
msgid "workspace.tokens.reference-error"
msgstr "Errores en referencias: "
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs:86
msgid "workspace.tokens.references-found"
msgstr "%s referencias encontradas en tu diseño"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs:105
msgid "workspace.tokens.remap-and-rename"
msgstr "Actualizar referencias y renombrar"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
#, unused
msgid "workspace.tokens.remap-explanation"
msgstr ""
"Todas las referencias a este token se actualizarán automáticamente para "
"usar el nuevo nombre."
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs:78
msgid "workspace.tokens.remap-token-references-title"
msgstr "¿Actualizar todas las referencias de `%s` a `%s`?"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs:78
msgid "workspace.tokens.remap-token-references"
msgstr "Actualizar referencias de token"
msgid "workspace.tokens.remap-warning-effects"
msgstr "Esta acción actualizará todas las capas y referencias que usen el token antiguo"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs:78
msgid "workspace.tokens.remap-warning-time"
msgstr "Este proceso puede durar un poco"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs:92
msgid "workspace.tokens.remapping-in-progress"
msgstr "Actualizando referencias de token..."
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs:106
msgid "workspace.tokens.rename-only"
msgstr "Renombrar"
msgid "workspace.tokens.not-remap"
msgstr "No actualizar"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs:83
msgid "workspace.tokens.renaming-token-from-to"
msgstr "Renombrando el token de '%s' a '%s'"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs:105
msgid "workspace.tokens.remap"
msgstr "Actualizar tokens"
#: src/app/main/data/workspace/tokens/warnings.cljs:15, src/app/main/data/workspace/tokens/warnings.cljs:19, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:56, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:84, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:103, src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs:271, src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs:445, src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs:169, src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs:304, src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs:225, src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs:332, src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs:432, src/app/main/ui/workspace/tokens/management/token_pill.cljs:121
#, fuzzy