mirror of
https://github.com/penpot/penpot.git
synced 2026-01-14 09:20:01 -05:00
Compare commits
7 Commits
nitrate-mo
...
eva-replac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bd0f835ee | ||
|
|
dd2d03e6a0 | ||
|
|
c98373658e | ||
|
|
01e42b0458 | ||
|
|
7529673812 | ||
|
|
d2dad35d7a | ||
|
|
d71f811dbe |
38
.github/ISSUE_TEMPLATE/new-render-bug-report.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/new-render-bug-report.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: New Render Bug Report
|
||||
about: Create a report about the bugs you have found in the new render
|
||||
title: ''
|
||||
labels: new render
|
||||
assignees: claragvinola
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Steps to Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots or screen recordings**
|
||||
If applicable, add screenshots or screen recording to help illustrate your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -21,7 +21,6 @@
|
||||
.rebel_readline_history
|
||||
.repl
|
||||
.shadow-cljs
|
||||
.pnpm-store/
|
||||
/*.jpg
|
||||
/*.md
|
||||
/*.png
|
||||
@@ -73,7 +72,6 @@
|
||||
/library/target/
|
||||
/library/*.zip
|
||||
/external
|
||||
/penpot-nitrate
|
||||
|
||||
clj-profiler/
|
||||
node_modules
|
||||
|
||||
@@ -36,8 +36,7 @@ export PENPOT_FLAGS="\
|
||||
enable-file-validation \
|
||||
enable-file-schema-validation \
|
||||
enable-redis-cache \
|
||||
enable-subscriptions \
|
||||
enable-nitrate";
|
||||
enable-subscriptions";
|
||||
|
||||
# Default deletion delay for devenv
|
||||
export PENPOT_DELETION_DELAY="24h"
|
||||
@@ -56,8 +55,6 @@ 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
|
||||
|
||||
export JAVA_OPTS="\
|
||||
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
|
||||
-Djdk.attach.allowAttachSelf \
|
||||
|
||||
@@ -225,8 +225,6 @@
|
||||
[: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]
|
||||
|
||||
@@ -323,7 +323,6 @@
|
||||
{::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)
|
||||
@@ -340,9 +339,6 @@
|
||||
::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)
|
||||
@@ -352,7 +348,6 @@
|
||||
::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)}
|
||||
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(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]))
|
||||
|
||||
|
||||
(def baseuri (cf/get :nitrate-backend-uri))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; HELPERS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn- coercer
|
||||
[schema & {:as opts}]
|
||||
(let [decode-fn (sm/decoder schema sm/json-transformer)
|
||||
check-fn (sm/check-fn schema opts)]
|
||||
(fn [data]
|
||||
(-> data decode-fn check-fn))))
|
||||
|
||||
|
||||
(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 (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]])
|
||||
|
||||
|
||||
(defn- get-team-org
|
||||
[cfg {:keys [team-id] :as params}]
|
||||
(request-to-nitrate cfg :get (str baseuri "/api/teams/" (str team-id)) schema:organization 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)})
|
||||
{}))
|
||||
|
||||
(defmethod ig/halt-key! ::client
|
||||
[_ {:keys []}]
|
||||
(do :stuff))
|
||||
@@ -301,7 +301,6 @@
|
||||
(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 {}))))
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
[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]
|
||||
@@ -173,12 +172,6 @@
|
||||
(map decode-row)
|
||||
(map process-permissions)))
|
||||
|
||||
(defn- add-org-to-team
|
||||
[cfg team params]
|
||||
(let [params (assoc (or params {}) :team-id (:id team))
|
||||
org (nitrate/call cfg :get-team-org params)]
|
||||
(assoc team :organization-id (:id org) :organization-name (:name org))))
|
||||
|
||||
(defn get-teams
|
||||
[conn profile-id]
|
||||
(let [profile (profile/get-profile conn profile-id)
|
||||
@@ -197,9 +190,7 @@
|
||||
::sm/params schema:get-teams}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
|
||||
(dm/with-open [conn (db/open pool)]
|
||||
(cond->> (get-teams conn profile-id)
|
||||
(contains? cf/flags :nitrate)
|
||||
(map #(add-org-to-team cfg % params)))))
|
||||
(get-teams conn profile-id)))
|
||||
|
||||
(def ^:private sql:get-owned-teams
|
||||
"SELECT t.id, t.name,
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(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}))))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -145,10 +145,7 @@
|
||||
|
||||
;; A temporal flag, enables backend code use more extensivelly
|
||||
;; redis for caching data
|
||||
:redis-cache
|
||||
|
||||
;; Activates the nitrate module
|
||||
:nitrate})
|
||||
:redis-cache})
|
||||
|
||||
(def all-flags
|
||||
(set/union email login varia))
|
||||
|
||||
@@ -487,6 +487,7 @@
|
||||
:vertical-margin #{:spacing :dimensions}
|
||||
:sided-margins #{:spacing :dimensions}
|
||||
:line-height #{:line-height :number}
|
||||
:opacity #{:opacity}
|
||||
:font-size #{:font-size}
|
||||
:letter-spacing #{:letter-spacing}
|
||||
:fill #{:color}
|
||||
|
||||
@@ -41,9 +41,4 @@ tmux select-window -t penpot:3
|
||||
tmux send-keys -t penpot 'cd penpot/backend' enter C-l
|
||||
tmux send-keys -t penpot './scripts/start-dev' enter
|
||||
|
||||
tmux new-window -t penpot:5 -n 'nitrate'
|
||||
tmux select-window -t penpot:5
|
||||
tmux send-keys -t penpot 'cd penpot/penpot-nitrate' enter C-l
|
||||
tmux send-keys -t penpot 'pnpm dev --host' enter
|
||||
|
||||
tmux -2 attach-session -t penpot
|
||||
|
||||
@@ -29,9 +29,8 @@ 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_NITRATE_URI,\$PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE" \
|
||||
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_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)"
|
||||
|
||||
@@ -139,14 +139,6 @@ 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 / {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source ~/.bashrc
|
||||
|
||||
set -ex
|
||||
|
||||
corepack enable;
|
||||
corepack install;
|
||||
|
||||
rm -rf ./_dist
|
||||
yarn
|
||||
yarn install
|
||||
yarn run build
|
||||
|
||||
@@ -50,7 +50,7 @@ const setupTokensFile = async (page, options = {}) => {
|
||||
const {
|
||||
file = "workspace/get-file-tokens.json",
|
||||
fileFragment = "workspace/get-file-fragment-tokens.json",
|
||||
flags = [],
|
||||
flags = ["enable-feature-token-input"],
|
||||
} = options;
|
||||
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
@@ -2242,6 +2242,56 @@ test.describe("Tokens: Apply token", () => {
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("User applies border-radius token to a shape from sidebar", async ({ page }) => {
|
||||
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
|
||||
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
|
||||
|
||||
// Open tokens sections on left sidebar
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
|
||||
// Unfold border radius tokens
|
||||
await page.getByRole("button", { name: "Border Radius 3" }).click();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "borderRadius" }),
|
||||
).toBeVisible();
|
||||
await tokensSidebar.getByRole("button", { name: "borderRadius" }).click();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "borderRadius.sm" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Apply border radius token from token panels
|
||||
await tokensSidebar.getByRole("button", { name: "borderRadius.sm" }).click();
|
||||
|
||||
// Check if border radius sections is visible on right sidebar
|
||||
const borderRadiusSection = page.getByRole("region", {name: "border-radius-section"});
|
||||
await expect(borderRadiusSection).toBeVisible();
|
||||
|
||||
// Check if token pill is visible on design tab on right sidebar
|
||||
const brTokenPillSM = borderRadiusSection.getByRole('button', { name: 'borderRadius.sm' });
|
||||
await expect(brTokenPillSM).toBeVisible();
|
||||
await brTokenPillSM.click();
|
||||
|
||||
// Change token from dropdown
|
||||
const brTokenOptionXl = borderRadiusSection.getByLabel('borderRadius.xl')
|
||||
await expect(brTokenOptionXl).toBeVisible();
|
||||
await brTokenOptionXl.click();
|
||||
|
||||
await expect(brTokenPillSM).not.toBeVisible();
|
||||
const brTokenPillXL = borderRadiusSection.getByRole('button', { name: 'borderRadius.xl' });
|
||||
await expect(brTokenPillXL).toBeVisible();
|
||||
|
||||
// Detach token from design tab on right sidebar
|
||||
const detachButton = borderRadiusSection.getByRole('button', { name: 'Detach token' });
|
||||
await detachButton.click();
|
||||
await expect(brTokenPillXL).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("User applies typography token to a text shape", async ({ page }) => {
|
||||
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTypographyTokensFile(page);
|
||||
@@ -2417,12 +2467,13 @@ test.describe("Tokens: Apply token", () => {
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill(newTokenTitle);
|
||||
|
||||
const referenceTabButton =
|
||||
tokensUpdateCreateModal.getByRole('button', { name: 'Use a reference' });
|
||||
const referenceTabButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Use a reference",
|
||||
});
|
||||
referenceTabButton.click();
|
||||
|
||||
const referenceField = tokensUpdateCreateModal.getByRole('textbox', {
|
||||
name: 'Reference'
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Reference",
|
||||
});
|
||||
await referenceField.fill("{Full}");
|
||||
|
||||
@@ -2782,14 +2833,18 @@ test.describe("Tokens: Remapping Feature", () => {
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
nameField = tokensUpdateCreateModal.getByRole("textbox", {name: "Name"});
|
||||
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"});
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Reference",
|
||||
});
|
||||
await referenceField.fill("{base-shadow}");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
@@ -2878,7 +2933,9 @@ test.describe("Tokens: Remapping Feature", () => {
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
await referenceToggle.click();
|
||||
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {name: "Reference"});
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Reference",
|
||||
});
|
||||
await referenceField.fill("{primary-shadow}");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
@@ -2950,7 +3007,8 @@ test.describe("Tokens: Remapping Feature", () => {
|
||||
|
||||
// Verify the shape still has the shadow applied with the UPDATED color value
|
||||
// Expand the shadow section to access the color field
|
||||
const shadowSection = workspacePage.rightSidebar.getByTestId("shadow-section");
|
||||
const shadowSection =
|
||||
workspacePage.rightSidebar.getByTestId("shadow-section");
|
||||
await expect(shadowSection).toBeVisible();
|
||||
|
||||
// Click to expand the shadow options (the menu button)
|
||||
@@ -3008,14 +3066,18 @@ test.describe("Tokens: Remapping Feature", () => {
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
nameField = tokensUpdateCreateModal.getByRole("textbox", {name: "Name"});
|
||||
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"})
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Reference",
|
||||
});
|
||||
await referenceField.fill("{base-text}");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
@@ -3096,14 +3158,18 @@ test.describe("Tokens: Remapping Feature", () => {
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
nameField = tokensUpdateCreateModal.getByRole("textbox", {name: "Name"});
|
||||
nameField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Name",
|
||||
});
|
||||
await nameField.fill("paragraph-style");
|
||||
|
||||
const referenceToggle =
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
await referenceToggle.click();
|
||||
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {name: "Reference"});
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Reference",
|
||||
});
|
||||
await referenceField.fill("{body-style}");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
|
||||
@@ -683,24 +683,12 @@
|
||||
(rx/of (dcm/change-team-role params)
|
||||
(modal/hide)))))
|
||||
|
||||
(defn handle-change-team-org
|
||||
[{:keys [team-id organization-id organization-name] :as message}]
|
||||
(ptk/reify ::handle-change-team-org
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(if (contains? (:teams state) team-id)
|
||||
(-> state
|
||||
(assoc-in [:teams team-id :organization-id] organization-id)
|
||||
(assoc-in [:teams team-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))
|
||||
|
||||
|
||||
|
||||
@@ -633,6 +633,43 @@
|
||||
:shape-ids shape-ids
|
||||
:on-update-shape on-update-shape}))))))))
|
||||
|
||||
(defn toggle-border-radius-token
|
||||
[{:keys [token attrs shape-ids expand-with-children]}]
|
||||
(ptk/reify ::on-toggle-border-radius-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [objects (dsh/lookup-page-objects state)
|
||||
shapes (into [] (keep (d/getf objects)) shape-ids)
|
||||
|
||||
shapes
|
||||
(if expand-with-children
|
||||
(into []
|
||||
(mapcat (fn [shape]
|
||||
(if (= (:type shape) :group)
|
||||
(keep objects (:shapes shape))
|
||||
[shape])))
|
||||
shapes)
|
||||
shapes)
|
||||
|
||||
{:keys [attributes all-attributes]}
|
||||
(get token-properties (:type token))
|
||||
|
||||
unapply-tokens?
|
||||
(cft/shapes-token-applied? token shapes (or attrs all-attributes attributes))
|
||||
|
||||
shape-ids (map :id shapes)]
|
||||
|
||||
(if unapply-tokens?
|
||||
(rx/of
|
||||
(unapply-token {:attributes (or attrs all-attributes attributes)
|
||||
:token token
|
||||
:shape-ids shape-ids}))
|
||||
(rx/of
|
||||
(apply-token {:attributes attrs
|
||||
:token token
|
||||
:shape-ids shape-ids
|
||||
:on-update-shape update-shape-radius-for-corners})))))))
|
||||
|
||||
|
||||
(defn apply-token-on-selected
|
||||
[color-operations token]
|
||||
|
||||
@@ -280,8 +280,8 @@
|
||||
|
||||
(mf/defc teams-selector-dropdown*
|
||||
{::mf/private true}
|
||||
[{:keys [team profile teams show-default-team allow-create-teams allow-create-org] :rest props}]
|
||||
(let [on-create-team-click
|
||||
[{:keys [team profile teams] :rest props}]
|
||||
(let [on-create-click
|
||||
(mf/use-fn #(st/emit! (modal/show :team-form {})))
|
||||
|
||||
on-team-click
|
||||
@@ -290,25 +290,18 @@
|
||||
(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)))))
|
||||
|
||||
on-create-org-click
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
;; TODO update when org creation route is ready
|
||||
(dom/open-new-window "localhost:3000/org/create")))]
|
||||
(st/emit! (dcm/go-to-dashboard-recent :team-id team-id)))))]
|
||||
|
||||
[:> dropdown-menu* props
|
||||
|
||||
(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]
|
||||
[:> 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
|
||||
@@ -329,19 +322,11 @@
|
||||
(when (= (:id team-item) (:id team))
|
||||
tick-icon)])
|
||||
|
||||
(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")]])]))
|
||||
[: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")]]]))
|
||||
|
||||
(mf/defc team-options-dropdown*
|
||||
{::mf/private true}
|
||||
@@ -491,80 +476,9 @@
|
||||
: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 [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)))
|
||||
(let [teams (mf/deref refs/teams)
|
||||
|
||||
subscription
|
||||
(get team :subscription)
|
||||
@@ -672,10 +586,7 @@
|
||||
:class (stl/css :dropdown :teams-dropdown)
|
||||
:team team
|
||||
:profile profile
|
||||
:teams teams
|
||||
:show-default-team true
|
||||
:allow-create-teams true
|
||||
:allow-create-org false}]
|
||||
:teams teams}]
|
||||
|
||||
[:> team-options-dropdown* {:show show-team-options-menu?
|
||||
:on-close close-team-options-menu
|
||||
@@ -792,8 +703,6 @@
|
||||
[:*
|
||||
[: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
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
[:map
|
||||
[:id {:optional true} :string]
|
||||
[:class {:optional true} :string]
|
||||
[:inner-class {:optional true} :string]
|
||||
[:value {:optional true} [:maybe [:or
|
||||
:int
|
||||
:float
|
||||
@@ -209,12 +210,13 @@
|
||||
|
||||
(mf/defc numeric-input*
|
||||
{::mf/schema schema:numeric-input}
|
||||
[{:keys [id class value default placeholder icon disabled
|
||||
[{:keys [id class value default placeholder
|
||||
icon disabled inner-class
|
||||
min max max-length step
|
||||
is-selected-on-focus nillable
|
||||
tokens applied-token empty-to-end
|
||||
on-change on-blur on-focus on-detach
|
||||
property align ref]
|
||||
property align ref name]
|
||||
:rest props}]
|
||||
|
||||
(let [;; NOTE: we use mfu/bean here for transparently handle
|
||||
@@ -624,6 +626,7 @@
|
||||
(mf/spread-props props {:ref ref
|
||||
:type "text"
|
||||
:id id
|
||||
:class inner-class
|
||||
:placeholder (if is-multiple?
|
||||
(tr "labels.mixed-values")
|
||||
placeholder)
|
||||
@@ -644,7 +647,7 @@
|
||||
:class (stl/css :icon)}]]))
|
||||
:slot-end (when-not disabled
|
||||
(when (some? tokens)
|
||||
(mf/html [:> icon-button* {:variant "action"
|
||||
(mf/html [:> icon-button* {:variant "ghost"
|
||||
:icon i/tokens
|
||||
:class (stl/css :invisible-button)
|
||||
:aria-label (tr "ds.inputs.numeric-input.open-token-list-dropdown")
|
||||
@@ -659,7 +662,10 @@
|
||||
label (get token :name)
|
||||
token-value (or (get token :resolved-value)
|
||||
(or (mf/ref-val last-value*)
|
||||
(fmt/format-number value)))]
|
||||
(fmt/format-number value)))
|
||||
token-value (if (= name :opacity)
|
||||
(* 100 token-value)
|
||||
token-value)]
|
||||
(mf/spread-props props
|
||||
{:id id
|
||||
:label label
|
||||
@@ -669,6 +675,7 @@
|
||||
:on-token-key-down on-token-key-down
|
||||
:disabled disabled
|
||||
:on-blur on-blur
|
||||
:class inner-class
|
||||
:slot-start (when icon
|
||||
(mf/html [:> tooltip*
|
||||
{:content property
|
||||
|
||||
@@ -33,12 +33,17 @@
|
||||
}
|
||||
|
||||
.invisible-button {
|
||||
position: absolute;
|
||||
inset-inline-end: 0;
|
||||
inset-block-start: 0;
|
||||
opacity: var(--opacity-button);
|
||||
|
||||
background-color: var(--color-background-quaternary);
|
||||
&:hover {
|
||||
background-color: var(--color-background-quaternary);
|
||||
--opacity-button: 1;
|
||||
}
|
||||
&:focus {
|
||||
background-color: var(--color-background-quaternary);
|
||||
--opacity-button: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
[:map
|
||||
[:id {:optional true} :string]
|
||||
[:resolved-value {:optional true}
|
||||
[:or :int :string]]
|
||||
[:or :int :string :float]]
|
||||
[:name {:optional true} :string]
|
||||
[:icon {:optional true} schema:icon-list]
|
||||
[:label {:optional true} :string]
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
@use "ds/_borders.scss" as *;
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/typography.scss" as *;
|
||||
@use "ds/_utils.scss" as *;
|
||||
|
||||
.option-list {
|
||||
--options-dropdown-icon-fg-color: var(--color-foreground-secondary);
|
||||
@@ -15,32 +16,32 @@
|
||||
--options-dropdown-border-color: var(--color-background-quaternary);
|
||||
|
||||
position: absolute;
|
||||
top: $sz-36;
|
||||
width: var(--dropdown-width, 100%);
|
||||
inset-block-start: $sz-36;
|
||||
inline-size: var(--dropdown-width, 100%);
|
||||
transform: translateX(var(--dropdown-translate-distance, 0));
|
||||
background-color: var(--options-dropdown-bg-color);
|
||||
border-radius: $br-8;
|
||||
border: $b-1 solid var(--options-dropdown-border-color);
|
||||
padding-block: var(--sp-xs);
|
||||
margin-block-end: 0;
|
||||
max-height: $sz-400;
|
||||
max-block-size: $sz-400;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
z-index: var(--z-index-dropdown);
|
||||
}
|
||||
|
||||
.left-align {
|
||||
left: 0;
|
||||
inset-inline-start: var(--dropdown-offset, 0);
|
||||
}
|
||||
|
||||
.right-align {
|
||||
right: 0;
|
||||
inset-inline-end: var(--dropdown-offset, 0);
|
||||
}
|
||||
|
||||
.option-separator {
|
||||
border: $b-1 solid var(--options-dropdown-border-color);
|
||||
margin-top: var(--sp-xs);
|
||||
margin-bottom: var(--sp-xs);
|
||||
margin-block-start: var(--sp-xs);
|
||||
margin-block-end: var(--sp-xs);
|
||||
}
|
||||
|
||||
.group-option,
|
||||
@@ -51,11 +52,11 @@
|
||||
gap: var(--sp-xs);
|
||||
color: var(--color-foreground-secondary);
|
||||
padding-inline: var(--sp-s);
|
||||
height: var(--sp-xxxl);
|
||||
block-size: var(--sp-xxxl);
|
||||
}
|
||||
|
||||
.option-empty {
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 0 40px;
|
||||
padding: 0 px2rem(40);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
[:map
|
||||
[:id {:optiona true} :string]
|
||||
[:ref some?]
|
||||
[:resolved {:optional true} [:or :int :string]]
|
||||
[:resolved {:optional true} [:or :int :string :float]]
|
||||
[:name {:optional true} :string]
|
||||
[:on-click {:optional true} fn?]
|
||||
[:selected {:optional true} :boolean]
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
(def ^:private schema:input-field
|
||||
[:map
|
||||
[:class {:optional true} :string]
|
||||
[:class {:optional true} [:maybe :string]]
|
||||
[:aria-label {:optional true} [:maybe :string]]
|
||||
[:id :string]
|
||||
[:icon {:optional true}
|
||||
@@ -44,9 +44,10 @@
|
||||
tooltip-id (mf/use-id)
|
||||
|
||||
props (mf/spread-props props
|
||||
{:class (stl/css-case
|
||||
:input true
|
||||
:input-with-icon (some? icon))
|
||||
{:class [class
|
||||
(stl/css-case
|
||||
:input true
|
||||
:input-with-icon (some? icon))]
|
||||
:ref (or ref input-ref)
|
||||
:aria-invalid (when (and has-hint
|
||||
(= hint-type "error"))
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
(def ^:private schema:token-field
|
||||
[:map
|
||||
[:class {:optional true} [:maybe :string]]
|
||||
[:id {:optional true} [:maybe :string]]
|
||||
[:label {:optional true} [:maybe :string]]
|
||||
[:value :any]
|
||||
@@ -32,7 +33,7 @@
|
||||
|
||||
(mf/defc token-field*
|
||||
{::mf/schema schema:token-field}
|
||||
[{:keys [id label value slot-start disabled
|
||||
[{:keys [id label value slot-start disabled class
|
||||
on-click on-token-key-down on-blur detach-token
|
||||
token-wrapper-ref token-detach-btn-ref on-focus]}]
|
||||
(let [set-active? (some? id)
|
||||
@@ -48,14 +49,11 @@
|
||||
(fn [event]
|
||||
(when-not ^boolean disabled
|
||||
(dom/prevent-default event)
|
||||
(dom/focus! (mf/ref-val token-wrapper-ref)))))
|
||||
(dom/focus! (mf/ref-val token-wrapper-ref)))))]
|
||||
|
||||
class
|
||||
(stl/css-case :token-field true
|
||||
:with-icon (some? slot-start)
|
||||
:token-field-disabled disabled)]
|
||||
|
||||
[:div {:class class
|
||||
[:div {:class [class (stl/css-case :token-field true
|
||||
:with-icon (some? slot-start)
|
||||
:token-field-disabled disabled)]
|
||||
:on-click focus-wrapper
|
||||
:disabled disabled
|
||||
:on-key-down on-token-key-down
|
||||
@@ -80,7 +78,7 @@
|
||||
[:div {:class (stl/css :pill-dot)}])]]
|
||||
|
||||
(when-not ^boolean disabled
|
||||
[:> icon-button* {:variant "action"
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:class (stl/css :invisible-button)
|
||||
:icon i/broken-link
|
||||
:ref token-detach-btn-ref
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/typography.scss" as t;
|
||||
@use "ds/colors.scss" as *;
|
||||
@use "ds/mixins.scss" as *;
|
||||
|
||||
.token-field {
|
||||
--token-field-bg-color: var(--color-background-tertiary);
|
||||
@@ -16,9 +17,7 @@
|
||||
--token-field-outline-color: none;
|
||||
--token-field-height: var(--sp-xxxl);
|
||||
--token-field-margin: unset;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
column-gap: var(--sp-xs);
|
||||
align-items: center;
|
||||
position: relative;
|
||||
@@ -27,6 +26,7 @@
|
||||
border-radius: $br-8;
|
||||
padding: var(--sp-xs);
|
||||
outline: $b-1 solid var(--token-field-outline-color);
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
--token-field-bg-color: var(--color-background-quaternary);
|
||||
@@ -39,7 +39,7 @@
|
||||
}
|
||||
|
||||
.with-icon {
|
||||
grid-template-columns: auto 1fr auto;
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
|
||||
.token-field-disabled {
|
||||
@@ -57,14 +57,17 @@
|
||||
--pill-bg-color: var(--color-background-tertiary);
|
||||
--pill-fg-color: var(--color-token-foreground);
|
||||
@include t.use-typography("code-font");
|
||||
height: var(--sp-xxl);
|
||||
width: fit-content;
|
||||
@include textEllipsis;
|
||||
display: block;
|
||||
block-size: var(--sp-xxl);
|
||||
inline-size: fit-content;
|
||||
background: var(--pill-bg-color);
|
||||
cursor: pointer;
|
||||
border: $b-1 solid var(--pill-border-color);
|
||||
color: var(--pill-fg-color);
|
||||
border-radius: $br-6;
|
||||
padding-inline: $sz-6;
|
||||
max-inline-size: 100%;
|
||||
&:hover {
|
||||
--pill-bg-color: var(--color-token-background);
|
||||
--pill-fg-color: var(--color-foreground-primary);
|
||||
@@ -103,24 +106,29 @@
|
||||
}
|
||||
|
||||
.pill-dot {
|
||||
width: $sz-6;
|
||||
height: $sz-6;
|
||||
inline-size: $sz-6;
|
||||
block-size: $sz-6;
|
||||
outline: var(--sp-xxs) solid var(--color-background-primary);
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-foreground-error);
|
||||
margin-left: var(--sp-xs);
|
||||
margin-inline-start: var(--sp-xs);
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
inset-inline-end: 0;
|
||||
inset-block-start: 0;
|
||||
}
|
||||
|
||||
.invisible-button {
|
||||
position: absolute;
|
||||
inset-inline-end: 0;
|
||||
inset-block-start: 0;
|
||||
opacity: var(--opacity-button);
|
||||
|
||||
background-color: var(--color-background-quaternary);
|
||||
&:hover {
|
||||
background-color: var(--color-background-quaternary);
|
||||
--opacity-button: 1;
|
||||
}
|
||||
&:focus {
|
||||
background-color: var(--color-background-quaternary);
|
||||
--opacity-button: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,4 +159,6 @@ $arrow-side: 12px;
|
||||
block-size: fit-content;
|
||||
inline-size: fit-content;
|
||||
line-height: 0;
|
||||
display: grid;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@@ -3,10 +3,15 @@
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.shape.radius :as ctsr]
|
||||
[app.common.types.token :as tk]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.features :as features]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.numeric-input :as deprecated-input]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
@@ -21,11 +26,17 @@
|
||||
(defn- check-border-radius-menu-props
|
||||
[old-props new-props]
|
||||
(let [old-values (unchecked-get old-props "values")
|
||||
new-values (unchecked-get new-props "values")]
|
||||
new-values (unchecked-get new-props "values")
|
||||
old-applied-tokens (unchecked-get old-props "appliedTokens")
|
||||
new-applied-tokens (unchecked-get new-props "appliedTokens")]
|
||||
(and (identical? (unchecked-get old-props "class")
|
||||
(unchecked-get new-props "class"))
|
||||
(identical? (unchecked-get old-props "ids")
|
||||
(unchecked-get new-props "ids"))
|
||||
(identical? (unchecked-get old-props "shapes")
|
||||
(unchecked-get new-props "shapes"))
|
||||
(identical? old-applied-tokens
|
||||
new-applied-tokens)
|
||||
(identical? (get old-values :r1)
|
||||
(get new-values :r1))
|
||||
(identical? (get old-values :r2)
|
||||
@@ -35,13 +46,113 @@
|
||||
(identical? (get old-values :r4)
|
||||
(get new-values :r4)))))
|
||||
|
||||
(mf/defc numeric-input-wrapper*
|
||||
{::mf/private true}
|
||||
[{:keys [values name applied-tokens align on-detach radius] :rest props}]
|
||||
(let [tokens (mf/use-ctx muc/active-tokens-by-type)
|
||||
tokens (mf/with-memo [tokens name]
|
||||
(delay
|
||||
(-> (deref tokens)
|
||||
(select-keys (get tk/tokens-by-input name))
|
||||
(not-empty))))
|
||||
on-detach-attr
|
||||
(mf/use-fn
|
||||
(mf/deps on-detach name)
|
||||
#(on-detach % name))
|
||||
|
||||
r1-value (get applied-tokens :r1)
|
||||
all-token-equal? (and (seq applied-tokens) (all-equal? applied-tokens))
|
||||
all-values-equal? (all-equal? values)
|
||||
|
||||
applied-token (cond
|
||||
(not (seq applied-tokens))
|
||||
nil
|
||||
|
||||
(and (= radius :all) (or (not all-values-equal?) (not all-token-equal?)))
|
||||
:multiple
|
||||
|
||||
(and all-token-equal? all-values-equal? (= radius :all))
|
||||
r1-value
|
||||
|
||||
:else
|
||||
(get applied-tokens radius))
|
||||
|
||||
|
||||
placeholder (if (= radius :all)
|
||||
(cond
|
||||
(or (not all-values-equal?)
|
||||
(not all-token-equal?))
|
||||
(tr "settings.multiple")
|
||||
:else
|
||||
"--")
|
||||
|
||||
(cond
|
||||
(or (= :multiple (:applied-tokens values))
|
||||
(= :multiple (get values name)))
|
||||
(tr "settings.multiple")
|
||||
:else
|
||||
"--"))
|
||||
|
||||
|
||||
props (mf/spread-props props
|
||||
{:placeholder placeholder
|
||||
:applied-token applied-token
|
||||
:tokens (if (delay? tokens) @tokens tokens)
|
||||
:align align
|
||||
:on-detach on-detach-attr
|
||||
:value values})]
|
||||
[:> numeric-input* props]))
|
||||
|
||||
(mf/defc border-radius-menu*
|
||||
{::mf/wrap [#(mf/memo' % check-border-radius-menu-props)]}
|
||||
[{:keys [class ids values]}]
|
||||
(let [all-equal? (all-equal? values)
|
||||
[{:keys [class ids values applied-tokens]}]
|
||||
(let [token-numeric-inputs
|
||||
(features/use-feature "tokens/numeric-input")
|
||||
|
||||
all-values-equal? (all-equal? values)
|
||||
|
||||
radius-expanded* (mf/use-state false)
|
||||
radius-expanded (deref radius-expanded*)
|
||||
|
||||
;; DETACH
|
||||
on-detach-token
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [token attr]
|
||||
(st/emit! (dwta/unapply-token {:token (first token)
|
||||
:attributes #{attr}
|
||||
:shape-ids ids}))))
|
||||
|
||||
on-detach-all
|
||||
(mf/use-fn
|
||||
(mf/deps on-detach-token)
|
||||
(fn [token]
|
||||
(run! #(on-detach-token token %) [:r1 :r2 :r3 :r4])))
|
||||
|
||||
on-detach-r1
|
||||
(mf/use-fn
|
||||
(mf/deps on-detach-token)
|
||||
(fn [token]
|
||||
(on-detach-token token :r1)))
|
||||
|
||||
on-detach-r2
|
||||
(mf/use-fn
|
||||
(mf/deps on-detach-token)
|
||||
(fn [token]
|
||||
(on-detach-token token :r2)))
|
||||
|
||||
on-detach-r3
|
||||
(mf/use-fn
|
||||
(mf/deps on-detach-token)
|
||||
(fn [token]
|
||||
(on-detach-token token :r3)))
|
||||
|
||||
on-detach-r4
|
||||
(mf/use-fn
|
||||
(mf/deps on-detach-token)
|
||||
(fn [token]
|
||||
(on-detach-token token :r4)))
|
||||
|
||||
change-radius
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
@@ -54,31 +165,54 @@
|
||||
{:reg-objects? true
|
||||
:attrs [:r1 :r2 :r3 :r4]})))
|
||||
|
||||
change-one-radius
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [update-fn attr]
|
||||
(dwsh/update-shapes ids
|
||||
(fn [shape]
|
||||
(if (ctsr/has-radius? shape)
|
||||
(update-fn shape)
|
||||
shape))
|
||||
{:reg-objects? true
|
||||
:attrs [attr]})))
|
||||
|
||||
toggle-radius-mode
|
||||
(mf/use-fn
|
||||
(mf/deps radius-expanded)
|
||||
(fn []
|
||||
(swap! radius-expanded* not)))
|
||||
|
||||
|
||||
on-all-radius-change
|
||||
(mf/use-fn
|
||||
(mf/deps change-radius ids)
|
||||
(fn [value]
|
||||
(if (or (string? value) (number? value))
|
||||
(st/emit!
|
||||
(change-radius (fn [shape]
|
||||
(ctsr/set-radius-to-all-corners shape value))))
|
||||
(doseq [attr [:r1 :r2 :r3 :r4]]
|
||||
(st/emit!
|
||||
(dwta/toggle-token {:token (first value)
|
||||
:attrs #{attr}
|
||||
:shape-ids ids}))))))
|
||||
|
||||
|
||||
on-single-radius-change
|
||||
(mf/use-fn
|
||||
(mf/deps ids change-radius)
|
||||
(fn [value]
|
||||
(st/emit!
|
||||
(change-radius (fn [shape]
|
||||
(ctsr/set-radius-to-all-corners shape value))))))
|
||||
|
||||
|
||||
on-radius-4-change
|
||||
(mf/use-fn
|
||||
(mf/deps ids change-radius)
|
||||
(mf/deps change-one-radius ids)
|
||||
(fn [value attr]
|
||||
(st/emit! (change-radius #(ctsr/set-radius-to-single-corner % attr value)))))
|
||||
(if (or (string? value) (number? value))
|
||||
(st/emit! (change-one-radius #(ctsr/set-radius-to-single-corner % attr value) attr))
|
||||
(st/emit! (dwta/toggle-border-radius-token {:token (first value)
|
||||
:attrs #{attr}
|
||||
:shape-ids ids})))))
|
||||
|
||||
on-radius-r1-change #(on-radius-4-change % :r1)
|
||||
on-radius-r2-change #(on-radius-4-change % :r2)
|
||||
on-radius-r3-change #(on-radius-4-change % :r3)
|
||||
on-radius-r4-change #(on-radius-4-change % :r4)
|
||||
on-radius-r1-change #(on-single-radius-change % :r1)
|
||||
on-radius-r2-change #(on-single-radius-change % :r2)
|
||||
on-radius-r3-change #(on-single-radius-change % :r3)
|
||||
on-radius-r4-change #(on-single-radius-change % :r4)
|
||||
|
||||
expand-stream
|
||||
(mf/with-memo []
|
||||
@@ -92,58 +226,139 @@
|
||||
(mf/with-effect [ids]
|
||||
(reset! radius-expanded* false))
|
||||
|
||||
[:div {:class (dm/str class " " (stl/css :radius))}
|
||||
[:section {:class (dm/str class " " (stl/css :radius))
|
||||
:aria-label "border-radius-section"}
|
||||
(if (not radius-expanded)
|
||||
[:div {:class (stl/css :radius-1)
|
||||
:title (tr "workspace.options.radius")}
|
||||
[:> icon* {:icon-id i/corner-radius
|
||||
:size "s"
|
||||
:class (stl/css :icon)}]
|
||||
[:> deprecated-input/numeric-input*
|
||||
{:placeholder (cond
|
||||
(not all-equal?)
|
||||
(tr "settings.multiple")
|
||||
(= :multiple (:r1 values))
|
||||
(tr "settings.multiple")
|
||||
:else
|
||||
"--")
|
||||
:min 0
|
||||
:nillable true
|
||||
:on-change on-single-radius-change
|
||||
:value (if all-equal? (:r1 values) nil)}]]
|
||||
|
||||
[:div {:class (stl/css :radius-4)}
|
||||
[:div {:class (stl/css :small-input)}
|
||||
[:> deprecated-input/numeric-input*
|
||||
{:placeholder "--"
|
||||
:title (tr "workspace.options.radius-top-left")
|
||||
(if token-numeric-inputs
|
||||
[:> numeric-input-wrapper*
|
||||
{:on-change on-all-radius-change
|
||||
:on-detach on-detach-all
|
||||
:icon i/corner-radius
|
||||
:min 0
|
||||
:on-change on-radius-r1-change
|
||||
:value (:r1 values)}]]
|
||||
:name :border-radius
|
||||
:nillable true
|
||||
:property (tr "workspace.options.radius")
|
||||
:class (stl/css :radius-wrapper)
|
||||
:applied-tokens applied-tokens
|
||||
:radius :all
|
||||
:align :right
|
||||
:values (if all-values-equal?
|
||||
(if (nil? (:r1 values))
|
||||
0
|
||||
(:r1 values))
|
||||
nil)}]
|
||||
|
||||
[:div {:class (stl/css :small-input)}
|
||||
[:> deprecated-input/numeric-input*
|
||||
{:placeholder "--"
|
||||
:title (tr "workspace.options.radius-top-right")
|
||||
:min 0
|
||||
:on-change on-radius-r2-change
|
||||
:value (:r2 values)}]]
|
||||
[:div {:class (stl/css :radius-1)
|
||||
:title (tr "workspace.options.radius")}
|
||||
[:> icon* {:icon-id i/corner-radius
|
||||
:size "s"
|
||||
:class (stl/css :icon)}]
|
||||
[:> deprecated-input/numeric-input*
|
||||
{:placeholder (cond
|
||||
(not all-values-equal?)
|
||||
(tr "settings.multiple")
|
||||
(= :multiple (:r1 values))
|
||||
(tr "settings.multiple")
|
||||
:else
|
||||
"--")
|
||||
:min 0
|
||||
:nillable true
|
||||
:on-change on-all-radius-change
|
||||
:value (if all-values-equal?
|
||||
(if (nil? (:r1 values))
|
||||
0
|
||||
(:r1 values))
|
||||
nil)}]])
|
||||
|
||||
[:div {:class (stl/css :small-input)}
|
||||
[:> deprecated-input/numeric-input*
|
||||
{:placeholder "--"
|
||||
:title (tr "workspace.options.radius-bottom-left")
|
||||
:min 0
|
||||
:on-change on-radius-r4-change
|
||||
:value (:r4 values)}]]
|
||||
(if token-numeric-inputs
|
||||
[:div {:class (stl/css :radius-4)}
|
||||
[:> numeric-input-wrapper*
|
||||
{:on-change on-radius-r1-change
|
||||
:on-detach on-detach-r1
|
||||
:min 0
|
||||
:name :border-radius
|
||||
:property (tr "workspace.options.radius-top-left")
|
||||
:applied-tokens applied-tokens
|
||||
:radius :r1
|
||||
:align :right
|
||||
:class (stl/css :radius-wrapper :dropdown-offset)
|
||||
:inner-class (stl/css :no-icon-input)
|
||||
:values (:r1 values)}]
|
||||
|
||||
[:div {:class (stl/css :small-input)}
|
||||
[:> deprecated-input/numeric-input*
|
||||
{:placeholder "--"
|
||||
:title (tr "workspace.options.radius-bottom-right")
|
||||
:min 0
|
||||
:on-change on-radius-r3-change
|
||||
:value (:r3 values)}]]])
|
||||
[:> numeric-input-wrapper*
|
||||
{:on-change on-radius-r2-change
|
||||
:on-detach on-detach-r2
|
||||
:min 0
|
||||
:name :border-radius
|
||||
:nillable true
|
||||
:property (tr "workspace.options.radius-top-right")
|
||||
:applied-tokens applied-tokens
|
||||
:align :right
|
||||
:class (stl/css :radius-wrapper)
|
||||
:inner-class (stl/css :no-icon-input)
|
||||
:radius :r2
|
||||
:values (:r2 values)}]
|
||||
|
||||
[:> numeric-input-wrapper*
|
||||
{:on-change on-radius-r4-change
|
||||
:on-detach on-detach-r4
|
||||
:min 0
|
||||
:name :border-radius
|
||||
:nillable true
|
||||
:property (tr "workspace.options.radius-bottom-left")
|
||||
:applied-tokens applied-tokens
|
||||
:class (stl/css :radius-wrapper :dropdown-offset)
|
||||
:inner-class (stl/css :no-icon-input)
|
||||
:radius :r4
|
||||
:align :right
|
||||
:values (:r4 values)}]
|
||||
|
||||
[:> numeric-input-wrapper*
|
||||
{:on-change on-radius-r3-change
|
||||
:on-detach on-detach-r3
|
||||
:min 0
|
||||
:name :border-radius
|
||||
:nillable true
|
||||
:property (tr "workspace.options.radius-bottom-right")
|
||||
:applied-tokens applied-tokens
|
||||
:radius :r3
|
||||
:align :right
|
||||
:class (stl/css :radius-wrapper)
|
||||
:inner-class (stl/css :no-icon-input)
|
||||
:values (:r3 values)}]]
|
||||
|
||||
[:div {:class (stl/css :radius-4)}
|
||||
[:div {:class (stl/css :small-input)}
|
||||
[:> deprecated-input/numeric-input*
|
||||
{:placeholder "--"
|
||||
:title (tr "workspace.options.radius-top-left")
|
||||
:min 0
|
||||
:on-change on-radius-r1-change
|
||||
:value (:r1 values)}]]
|
||||
|
||||
[:div {:class (stl/css :small-input)}
|
||||
[:> deprecated-input/numeric-input*
|
||||
{:placeholder "--"
|
||||
:title (tr "workspace.options.radius-top-right")
|
||||
:min 0
|
||||
:on-change on-radius-r2-change
|
||||
:value (:r2 values)}]]
|
||||
|
||||
[:div {:class (stl/css :small-input)}
|
||||
[:> deprecated-input/numeric-input*
|
||||
{:placeholder "--"
|
||||
:title (tr "workspace.options.radius-bottom-left")
|
||||
:min 0
|
||||
:on-change on-radius-r4-change
|
||||
:value (:r4 values)}]]
|
||||
|
||||
[:div {:class (stl/css :small-input)}
|
||||
[:> deprecated-input/numeric-input*
|
||||
{:placeholder "--"
|
||||
:title (tr "workspace.options.radius-bottom-right")
|
||||
:min 0
|
||||
:on-change on-radius-r3-change
|
||||
:value (:r3 values)}]]]))
|
||||
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:on-click toggle-radius-mode
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
@use "ds/typography" as t;
|
||||
@use "ds/_utils.scss" as *;
|
||||
|
||||
.radius {
|
||||
display: grid;
|
||||
@@ -14,7 +16,7 @@
|
||||
|
||||
.radius-1 {
|
||||
@extend .input-element;
|
||||
@include deprecated.bodySmallTypography;
|
||||
@include t.use-typography("body-small");
|
||||
}
|
||||
|
||||
.radius-4 {
|
||||
@@ -25,9 +27,27 @@
|
||||
|
||||
.small-input {
|
||||
@extend .input-element;
|
||||
@include deprecated.bodySmallTypography;
|
||||
@include t.use-typography("body-small");
|
||||
}
|
||||
|
||||
.selected {
|
||||
border-color: var(--button-icon-border-color-selected);
|
||||
background-color: var(--button-icon-background-color-selected);
|
||||
color: var(--color-accent-primary);
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-inline: deprecated.$s-4;
|
||||
margin-inline: var(--sp-xs);
|
||||
}
|
||||
|
||||
.radius-wrapper {
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
}
|
||||
|
||||
.no-icon-input {
|
||||
padding-inline-start: px2rem(6);
|
||||
}
|
||||
|
||||
.dropdown-offset {
|
||||
--dropdown-offset: #{px2rem(-65)};
|
||||
}
|
||||
@@ -9,13 +9,17 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.token :as tk]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.features :as features]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.components.numeric-input :as deprecated-input]
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.render-wasm.api :as wasm.api]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
@@ -39,11 +43,16 @@
|
||||
(defn- check-layer-menu-props
|
||||
[old-props new-props]
|
||||
(let [old-values (unchecked-get old-props "values")
|
||||
new-values (unchecked-get new-props "values")]
|
||||
new-values (unchecked-get new-props "values")
|
||||
|
||||
old-applied-tokens (unchecked-get old-props "appliedTokens")
|
||||
new-applied-tokens (unchecked-get new-props "appliedTokens")]
|
||||
(and (identical? (unchecked-get old-props "class")
|
||||
(unchecked-get new-props "class"))
|
||||
(identical? (unchecked-get old-props "ids")
|
||||
(unchecked-get new-props "ids"))
|
||||
(identical? old-applied-tokens
|
||||
new-applied-tokens)
|
||||
(identical? (get old-values :opacity)
|
||||
(get new-values :opacity))
|
||||
(identical? (get old-values :blend-mode)
|
||||
@@ -53,12 +62,54 @@
|
||||
(identical? (get old-values :hidden)
|
||||
(get new-values :hidden)))))
|
||||
|
||||
(mf/defc numeric-input-wrapper*
|
||||
{::mf/private true}
|
||||
[{:keys [values name applied-tokens align on-detach] :rest props}]
|
||||
(let [tokens (mf/use-ctx muc/active-tokens-by-type)
|
||||
tokens (mf/with-memo [tokens name]
|
||||
(delay
|
||||
(-> (deref tokens)
|
||||
(select-keys (get tk/tokens-by-input name))
|
||||
(not-empty))))
|
||||
|
||||
on-detach-attr
|
||||
(mf/use-fn
|
||||
(mf/deps on-detach name)
|
||||
#(on-detach % name))
|
||||
|
||||
applied-token (get applied-tokens name)
|
||||
opacity-value (or (get values name) 1)
|
||||
|
||||
props (mf/spread-props props
|
||||
{:placeholder (if (or (= :multiple (:applied-tokens values))
|
||||
(= :multiple opacity-value))
|
||||
(tr "settings.multiple")
|
||||
"--")
|
||||
:applied-token applied-token
|
||||
:tokens (if (delay? tokens) @tokens tokens)
|
||||
:align align
|
||||
:on-detach on-detach-attr
|
||||
:name name
|
||||
:value (* 100 opacity-value)})]
|
||||
[:> numeric-input* props]))
|
||||
|
||||
(mf/defc layer-menu*
|
||||
{::mf/wrap [#(mf/memo' % check-layer-menu-props)]}
|
||||
[{:keys [ids values]}]
|
||||
(let [hidden? (get values :hidden)
|
||||
[{:keys [ids values applied-tokens]}]
|
||||
(let [token-numeric-inputs
|
||||
(features/use-feature "tokens/numeric-input")
|
||||
|
||||
hidden? (get values :hidden)
|
||||
blocked? (get values :blocked)
|
||||
|
||||
on-detach-token
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [token attr]
|
||||
(st/emit! (dwta/unapply-token {:token (first token)
|
||||
:attributes #{attr}
|
||||
:shape-ids ids}))))
|
||||
|
||||
current-blend-mode (or (get values :blend-mode) :normal)
|
||||
current-opacity (opacity->string (:opacity values))
|
||||
|
||||
@@ -118,6 +169,17 @@
|
||||
(let [value (/ value 100)]
|
||||
(on-change ids :opacity value))))
|
||||
|
||||
on-opacity-change
|
||||
(mf/use-fn
|
||||
(mf/deps on-change handle-opacity-change)
|
||||
(fn [value]
|
||||
(if (or (string? value) (int? value))
|
||||
(handle-opacity-change value)
|
||||
(do
|
||||
(st/emit! (dwta/toggle-token {:token (first value)
|
||||
:attrs #{:opacity}
|
||||
:shape-ids ids}))))))
|
||||
|
||||
handle-set-hidden
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
@@ -187,16 +249,34 @@
|
||||
:class (stl/css-case :hidden-select hidden?)
|
||||
:on-pointer-enter-option handle-blend-mode-enter
|
||||
:on-pointer-leave-option handle-blend-mode-leave}]]
|
||||
[:div {:class (stl/css :input)
|
||||
:title (tr "workspace.options.opacity")}
|
||||
[:span {:class (stl/css :icon)} "%"]
|
||||
[:> numeric-input*
|
||||
{:value current-opacity
|
||||
:placeholder "--"
|
||||
:on-change handle-opacity-change
|
||||
:min 0
|
||||
:max 100
|
||||
:className (stl/css :numeric-input)}]]
|
||||
|
||||
|
||||
|
||||
(if token-numeric-inputs
|
||||
|
||||
[:> numeric-input-wrapper*
|
||||
{:on-change on-opacity-change
|
||||
:on-detach on-detach-token
|
||||
:icon i/percentage
|
||||
:min 0
|
||||
:max 100
|
||||
:name :opacity
|
||||
:property (tr "workspace.options.opacity")
|
||||
:applied-tokens applied-tokens
|
||||
:align :right
|
||||
:class (stl/css :numeric-input-wrapper)
|
||||
:values values}]
|
||||
|
||||
[:div {:class (stl/css :input)
|
||||
:title (tr "workspace.options.opacity")}
|
||||
[:span {:class (stl/css :icon)} "%"]
|
||||
[:> deprecated-input/numeric-input*
|
||||
{:value current-opacity
|
||||
:placeholder "--"
|
||||
:on-change handle-opacity-change
|
||||
:min 0
|
||||
:max 100
|
||||
:className (stl/css :numeric-input)}]])
|
||||
|
||||
|
||||
[:div {:class (stl/css :actions)}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
@use "../../../sidebar/common/sidebar.scss" as sidebar;
|
||||
@use "ds/_utils.scss" as *;
|
||||
|
||||
.element-set-content {
|
||||
@include sidebar.option-grid-structure;
|
||||
@@ -43,3 +44,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.numeric-input-wrapper {
|
||||
grid-column: span 2;
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
--dropdown-offset: #{px2rem(-35)};
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
(nil? (get values name)))
|
||||
(tr "settings.multiple")
|
||||
"--")
|
||||
:class (stl/css :numeric-input-measures)
|
||||
:class (stl/css :numeric-input-layout)
|
||||
:applied-token (get applied-tokens name)
|
||||
:tokens tokens
|
||||
:align align
|
||||
|
||||
@@ -358,6 +358,6 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.numeric-input-measures {
|
||||
.numeric-input-layout {
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
}
|
||||
|
||||
@@ -600,10 +600,7 @@
|
||||
[:> border-radius-menu* {:class (stl/css :border-radius)
|
||||
:ids ids
|
||||
:values values
|
||||
:applied-tokens applied-tokens
|
||||
:shapes shapes
|
||||
:shape shape}])])
|
||||
|
||||
:applied-tokens applied-tokens}])])
|
||||
(when (or (options :clip-content)
|
||||
(options :show-in-viewer))
|
||||
[:div {:class (stl/css :clip-show)}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
@use "../../../sidebar/common/sidebar.scss" as sidebar;
|
||||
@use "ds/_utils.scss" as *;
|
||||
|
||||
.element-set {
|
||||
display: grid;
|
||||
@@ -156,7 +157,6 @@
|
||||
gap: deprecated.$s-4;
|
||||
}
|
||||
|
||||
// TODO: Add a proper variable to this sizing
|
||||
.numeric-input-measures {
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
[:*
|
||||
[:> layer-menu* {:ids ids
|
||||
:type type
|
||||
:applied-tokens applied-tokens
|
||||
:values layer-values}]
|
||||
|
||||
[:> measures-menu* {:ids ids
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
[:*
|
||||
[:> layer-menu* {:ids ids
|
||||
:type type
|
||||
:applied-tokens applied-tokens
|
||||
:values layer-values}]
|
||||
|
||||
[:> measures-menu* {:ids ids
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
[:*
|
||||
[:> layer-menu* {:ids ids
|
||||
:type shape-type
|
||||
:applied-tokens applied-tokens
|
||||
:values layer-values}]
|
||||
[:> measures-menu* {:ids ids
|
||||
:applied-tokens applied-tokens
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
[:div {:class (stl/css :options)}
|
||||
[:> layer-menu* {:type type
|
||||
:ids layer-ids
|
||||
:applied-tokens applied-tokens
|
||||
:values layer-values}]
|
||||
[:> measures-menu* {:type type
|
||||
:ids measure-ids
|
||||
|
||||
@@ -223,7 +223,6 @@
|
||||
(cond
|
||||
(= existing ::not-found) (assoc acc t-attr new-val)
|
||||
(= existing new-val) acc
|
||||
(nil? new-val) acc
|
||||
:else (assoc acc t-attr :multiple))))
|
||||
|
||||
merge-shape-attr
|
||||
@@ -237,10 +236,8 @@
|
||||
(fn [acc shape-attrs applied-tokens]
|
||||
"Merges token values across all shape attributes.
|
||||
For each shape attribute, its corresponding token attributes are merged
|
||||
into the accumulator. If applied tokens are empty, the accumulator is returned unchanged."
|
||||
(if (seq applied-tokens)
|
||||
(reduce #(merge-shape-attr %1 applied-tokens %2) acc shape-attrs)
|
||||
acc))
|
||||
into the accumulator."
|
||||
(reduce #(merge-shape-attr %1 applied-tokens %2) acc shape-attrs))
|
||||
|
||||
extract-attrs
|
||||
(fn [[ids values token-acc] {:keys [id type applied-tokens] :as shape}]
|
||||
@@ -385,7 +382,7 @@
|
||||
objects
|
||||
objects)))
|
||||
|
||||
[layer-ids layer-values]
|
||||
[layer-ids layer-values layer-tokens]
|
||||
(get-attrs shapes objects :layer)
|
||||
|
||||
[text-ids text-values]
|
||||
@@ -409,7 +406,7 @@
|
||||
[exports-ids exports-values]
|
||||
(get-attrs shapes objects :exports)
|
||||
|
||||
[layout-container-ids layout-container-values layout-contianer-tokens]
|
||||
[layout-container-ids layout-container-values layout-container-tokens]
|
||||
(get-attrs shapes objects :layout-container)
|
||||
|
||||
[layout-item-ids layout-item-values {}]
|
||||
@@ -445,6 +442,7 @@
|
||||
(when-not (empty? layer-ids)
|
||||
[:> layer-menu* {:type type
|
||||
:ids layer-ids
|
||||
:applied-tokens layer-tokens
|
||||
:values layer-values}])
|
||||
|
||||
(when-not (empty? measure-ids)
|
||||
@@ -462,7 +460,7 @@
|
||||
{:type type
|
||||
:ids layout-container-ids
|
||||
:values layout-container-values
|
||||
:applied-tokens layout-contianer-tokens
|
||||
:applied-tokens layout-container-tokens
|
||||
:multiple true}]
|
||||
|
||||
(when (or is-layout-child? has-flex-layout-container?)
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
|
||||
[:*
|
||||
[:> layer-menu* {:ids ids
|
||||
:applied-tokens applied-tokens
|
||||
:type type
|
||||
:values layer-values}]
|
||||
[:> measures-menu* {:ids ids
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
[:*
|
||||
[:> layer-menu* {:ids ids
|
||||
:type type
|
||||
:applied-tokens applied-tokens
|
||||
:values layer-values}]
|
||||
[:> measures-menu* {:ids ids
|
||||
:type type
|
||||
|
||||
@@ -125,6 +125,7 @@
|
||||
[:*
|
||||
[:> layer-menu* {:ids ids
|
||||
:type type
|
||||
:applied-tokens applied-tokens
|
||||
:values layer-values}]
|
||||
[:> measures-menu*
|
||||
{:ids ids
|
||||
|
||||
@@ -433,9 +433,6 @@ 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"
|
||||
|
||||
@@ -442,9 +442,6 @@ 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"
|
||||
|
||||
Reference in New Issue
Block a user