mirror of
https://github.com/penpot/penpot.git
synced 2026-05-30 04:08:08 +00:00
Merge branch 'staging' into develop
This commit is contained in:
commit
88424eb54a
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,6 +5,7 @@
|
|||||||
!.yarn/releases
|
!.yarn/releases
|
||||||
!.yarn/sdks
|
!.yarn/sdks
|
||||||
!.yarn/versions
|
!.yarn/versions
|
||||||
|
.pnpm-store
|
||||||
*-init.clj
|
*-init.clj
|
||||||
*.css.json
|
*.css.json
|
||||||
*.jar
|
*.jar
|
||||||
|
|||||||
@ -35,6 +35,7 @@
|
|||||||
- Fix problem when pasting elements in reverse flex layout [Taiga #12460](https://tree.taiga.io/project/penpot/issue/12460)
|
- Fix problem when pasting elements in reverse flex layout [Taiga #12460](https://tree.taiga.io/project/penpot/issue/12460)
|
||||||
- Fix wrong board size presets in Android [Taiga #12339](https://tree.taiga.io/project/penpot/issue/12339)
|
- Fix wrong board size presets in Android [Taiga #12339](https://tree.taiga.io/project/penpot/issue/12339)
|
||||||
- Fix problem with grid layout components and auto sizing [Github #7797](https://github.com/penpot/penpot/issues/7797)
|
- Fix problem with grid layout components and auto sizing [Github #7797](https://github.com/penpot/penpot/issues/7797)
|
||||||
|
- Fix some alignments on inspect tab [Taiga #12915](https://tree.taiga.io/project/penpot/issue/12915)
|
||||||
|
|
||||||
|
|
||||||
## 2.12.1
|
## 2.12.1
|
||||||
|
|||||||
@ -36,17 +36,6 @@
|
|||||||
[integrant.core :as ig]
|
[integrant.core :as ig]
|
||||||
[yetti.response :as-alias yres]))
|
[yetti.response :as-alias yres]))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;; HELPERS
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
(defn obfuscate-string
|
|
||||||
[s]
|
|
||||||
(if (< (count s) 10)
|
|
||||||
(apply str (take (count s) (repeat "*")))
|
|
||||||
(str (subs s 0 5)
|
|
||||||
(apply str (take (- (count s) 5) (repeat "*"))))))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; OIDC PROVIDER (GENERIC)
|
;; OIDC PROVIDER (GENERIC)
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@ -177,7 +166,7 @@
|
|||||||
(l/inf :hint "provider initialized"
|
(l/inf :hint "provider initialized"
|
||||||
:provider (:id provider)
|
:provider (:id provider)
|
||||||
:client-id (:client-id provider)
|
:client-id (:client-id provider)
|
||||||
:client-secret (obfuscate-string (:client-secret provider)))
|
:client-secret (d/obfuscate-string (:client-secret provider)))
|
||||||
provider)
|
provider)
|
||||||
|
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
@ -222,7 +211,7 @@
|
|||||||
(l/inf :hint "provider initialized"
|
(l/inf :hint "provider initialized"
|
||||||
:provider (:id provider)
|
:provider (:id provider)
|
||||||
:client-id (:client-id provider)
|
:client-id (:client-id provider)
|
||||||
:client-secret (obfuscate-string (:client-secret provider)))
|
:client-secret (d/obfuscate-string (:client-secret provider)))
|
||||||
provider)
|
provider)
|
||||||
|
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
@ -299,7 +288,7 @@
|
|||||||
(l/inf :hint "provider initialized"
|
(l/inf :hint "provider initialized"
|
||||||
:provider (:id provider)
|
:provider (:id provider)
|
||||||
:client-id (:client-id provider)
|
:client-id (:client-id provider)
|
||||||
:client-secret (obfuscate-string (:client-secret provider)))
|
:client-secret (d/obfuscate-string (:client-secret provider)))
|
||||||
provider)
|
provider)
|
||||||
|
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
@ -341,7 +330,7 @@
|
|||||||
:provider "gitlab"
|
:provider "gitlab"
|
||||||
:base-uri (:base-uri provider)
|
:base-uri (:base-uri provider)
|
||||||
:client-id (:client-id provider)
|
:client-id (:client-id provider)
|
||||||
:client-secret (obfuscate-string (:client-secret provider)))
|
:client-secret (d/obfuscate-string (:client-secret provider)))
|
||||||
provider)
|
provider)
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
(ex/raise :type ::internal
|
(ex/raise :type ::internal
|
||||||
@ -361,7 +350,7 @@
|
|||||||
(l/inf :hint "provider initialized"
|
(l/inf :hint "provider initialized"
|
||||||
:provider (:id provider)
|
:provider (:id provider)
|
||||||
:client-id (:client-id provider)
|
:client-id (:client-id provider)
|
||||||
:client-secret (obfuscate-string (:client-secret provider)))
|
:client-secret (d/obfuscate-string (:client-secret provider)))
|
||||||
provider)
|
provider)
|
||||||
|
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
@ -459,7 +448,7 @@
|
|||||||
(l/trc :hint "fetch access token"
|
(l/trc :hint "fetch access token"
|
||||||
:provider (:id provider)
|
:provider (:id provider)
|
||||||
:client-id (:client-id provider)
|
:client-id (:client-id provider)
|
||||||
:client-secret (obfuscate-string (:client-secret provider))
|
:client-secret (d/obfuscate-string (:client-secret provider))
|
||||||
:grant-type (:grant_type params)
|
:grant-type (:grant_type params)
|
||||||
:redirect-uri (:redirect_uri params))
|
:redirect-uri (:redirect_uri params))
|
||||||
|
|
||||||
@ -512,7 +501,7 @@
|
|||||||
[cfg provider tdata]
|
[cfg provider tdata]
|
||||||
(l/trc :hint "fetch user info"
|
(l/trc :hint "fetch user info"
|
||||||
:uri (:user-uri provider)
|
:uri (:user-uri provider)
|
||||||
:token (obfuscate-string (:token/access tdata)))
|
:token (d/obfuscate-string (:token/access tdata)))
|
||||||
|
|
||||||
(let [params {:uri (:user-uri provider)
|
(let [params {:uri (:user-uri provider)
|
||||||
:headers {"Authorization" (str (:token/type tdata) " " (:token/access tdata))}
|
:headers {"Authorization" (str (:token/type tdata) " " (:token/access tdata))}
|
||||||
|
|||||||
@ -1024,6 +1024,26 @@
|
|||||||
:clj
|
:clj
|
||||||
(sort comp-fn items))))
|
(sort comp-fn items))))
|
||||||
|
|
||||||
|
(defn obfuscate-string
|
||||||
|
"Obfuscates potentially sensitive values.
|
||||||
|
|
||||||
|
- One-arg arity:
|
||||||
|
* For strings shorter than 10 characters, all characters are replaced by `*`.
|
||||||
|
* For longer strings, the first 5 characters are preserved and the rest obfuscated.
|
||||||
|
- Two-arg arity accepts a boolean `full?` that, when true, replaces the whole value
|
||||||
|
by `*`, preserving only the length."
|
||||||
|
([v]
|
||||||
|
(obfuscate-string v false))
|
||||||
|
([v full?]
|
||||||
|
(let [s (str v)
|
||||||
|
n (count s)]
|
||||||
|
(cond
|
||||||
|
(zero? n) s
|
||||||
|
full? (apply str (repeat n "*"))
|
||||||
|
(< n 10) (apply str (repeat n "*"))
|
||||||
|
:else (str (subs s 0 5)
|
||||||
|
(apply str (repeat (- n 5) "*")))))))
|
||||||
|
|
||||||
(defn reorder
|
(defn reorder
|
||||||
"Reorder a vector by moving one of their items from some position to some space between positions.
|
"Reorder a vector by moving one of their items from some position to some space between positions.
|
||||||
It clamps the position numbers to a valid range."
|
It clamps the position numbers to a valid range."
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
(:refer-clojure :exclude [instance?])
|
(:refer-clojure :exclude [instance?])
|
||||||
(:require
|
(:require
|
||||||
#?(:clj [clojure.stacktrace :as strace])
|
#?(:clj [clojure.stacktrace :as strace])
|
||||||
|
[app.common.data :refer [obfuscate-string]]
|
||||||
[app.common.pprint :as pp]
|
[app.common.pprint :as pp]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[clojure.core :as c]
|
[clojure.core :as c]
|
||||||
@ -19,6 +20,10 @@
|
|||||||
(:import
|
(:import
|
||||||
clojure.lang.IPersistentMap)))
|
clojure.lang.IPersistentMap)))
|
||||||
|
|
||||||
|
(def ^:private sensitive-fields
|
||||||
|
"Keys whose values must be obfuscated in validation explains."
|
||||||
|
#{:password :old-password :token :invitation-token})
|
||||||
|
|
||||||
#?(:clj (set! *warn-on-reflection* true))
|
#?(:clj (set! *warn-on-reflection* true))
|
||||||
|
|
||||||
(def ^:dynamic *data-length* 8)
|
(def ^:dynamic *data-length* 8)
|
||||||
@ -110,7 +115,25 @@
|
|||||||
(explain (:explain data) opts)
|
(explain (:explain data) opts)
|
||||||
|
|
||||||
(contains? data ::sm/explain)
|
(contains? data ::sm/explain)
|
||||||
(sm/humanize-explain (::sm/explain data) opts)))
|
(let [exp (::sm/explain data)
|
||||||
|
sanitize-map (fn sanitize-map [m]
|
||||||
|
(reduce-kv
|
||||||
|
(fn [acc k v]
|
||||||
|
(let [k* (if (string? k) (keyword k) k)]
|
||||||
|
(cond
|
||||||
|
(contains? sensitive-fields k*)
|
||||||
|
(assoc acc k (if (map? v)
|
||||||
|
(sanitize-map v)
|
||||||
|
(obfuscate-string v true)))
|
||||||
|
|
||||||
|
(map? v) (assoc acc k (sanitize-map v))
|
||||||
|
:else (assoc acc k v))))
|
||||||
|
{}
|
||||||
|
m))
|
||||||
|
sanitize-explain (fn [exp]
|
||||||
|
(cond-> exp
|
||||||
|
(:value exp) (update :value sanitize-map)))]
|
||||||
|
(sm/humanize-explain (sanitize-explain exp) opts))))
|
||||||
|
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(defn format-throwable
|
(defn format-throwable
|
||||||
|
|||||||
@ -132,94 +132,3 @@ Some naming conventions:
|
|||||||
(if-let [last-period (str/last-index-of s ".")]
|
(if-let [last-period (str/last-index-of s ".")]
|
||||||
[(subs s 0 (inc last-period)) (subs s (inc last-period))]
|
[(subs s 0 (inc last-period)) (subs s (inc last-period))]
|
||||||
[s ""]))
|
[s ""]))
|
||||||
|
|
||||||
;; Tree building functions --------------------------------------------------
|
|
||||||
|
|
||||||
"Build tree structure from flat list of paths"
|
|
||||||
|
|
||||||
"`build-tree-root` is the main function to build the tree."
|
|
||||||
|
|
||||||
"Receives a list of segments with 'name' properties representing paths,
|
|
||||||
and a separator string."
|
|
||||||
"E.g segments = [{... :name 'one/two/three'} {... :name 'one/two/four'} {... :name 'one/five'}]"
|
|
||||||
|
|
||||||
"Transforms into a tree structure like:
|
|
||||||
[{:name 'one'
|
|
||||||
:path 'one'
|
|
||||||
:depth 0
|
|
||||||
:leaf nil
|
|
||||||
:children-fn (fn [] [{:name 'two'
|
|
||||||
:path 'one.two'
|
|
||||||
:depth 1
|
|
||||||
:leaf nil
|
|
||||||
:children-fn (fn [] [{... :name 'three'} {... :name 'four'}])}
|
|
||||||
{:name 'five'
|
|
||||||
:path 'one.five'
|
|
||||||
:depth 1
|
|
||||||
:leaf {... :name 'five'}
|
|
||||||
...}])}]"
|
|
||||||
|
|
||||||
(defn- sort-by-children
|
|
||||||
"Sorts segments so that those with children come first."
|
|
||||||
[segments separator]
|
|
||||||
(sort-by (fn [segment]
|
|
||||||
(let [path (split-path (:name segment) :separator separator)
|
|
||||||
path-length (count path)]
|
|
||||||
(if (= path-length 1)
|
|
||||||
1
|
|
||||||
0)))
|
|
||||||
segments))
|
|
||||||
|
|
||||||
(defn- group-by-first-segment
|
|
||||||
"Groups segments by their first path segment and update segment name."
|
|
||||||
[segments separator]
|
|
||||||
(reduce (fn [acc segment]
|
|
||||||
(let [[first-segment & remaining-segments] (split-path (:name segment) :separator separator)
|
|
||||||
rest-path (when (seq remaining-segments) (join-path remaining-segments :separator separator :with-spaces? false))]
|
|
||||||
(update acc first-segment (fnil conj [])
|
|
||||||
(if rest-path
|
|
||||||
(assoc segment :name rest-path)
|
|
||||||
segment))))
|
|
||||||
{}
|
|
||||||
segments))
|
|
||||||
|
|
||||||
(defn- sort-and-group-segments
|
|
||||||
"Sorts elements and groups them by their first path segment."
|
|
||||||
[segments separator]
|
|
||||||
(let [sorted (sort-by-children segments separator)
|
|
||||||
grouped (group-by-first-segment sorted separator)]
|
|
||||||
grouped))
|
|
||||||
|
|
||||||
(defn- build-tree-node
|
|
||||||
"Builds a single tree node with lazy children."
|
|
||||||
[segment-name remaining-segments separator parent-path depth]
|
|
||||||
(let [current-path (if parent-path
|
|
||||||
(str parent-path "." segment-name)
|
|
||||||
segment-name)
|
|
||||||
|
|
||||||
is-leaf? (and (seq remaining-segments)
|
|
||||||
(every? (fn [segment]
|
|
||||||
(let [remaining-segment-name (first (split-path (:name segment) :separator separator))]
|
|
||||||
(= segment-name remaining-segment-name)))
|
|
||||||
remaining-segments))
|
|
||||||
|
|
||||||
leaf-segment (when is-leaf? (first remaining-segments))
|
|
||||||
node {:name segment-name
|
|
||||||
:path current-path
|
|
||||||
:depth depth
|
|
||||||
:leaf leaf-segment
|
|
||||||
:children-fn (when-not is-leaf?
|
|
||||||
(fn []
|
|
||||||
(let [grouped-elements (sort-and-group-segments remaining-segments separator)]
|
|
||||||
(mapv (fn [[child-segment-name remaining-child-segments]]
|
|
||||||
(build-tree-node child-segment-name remaining-child-segments separator current-path (inc depth)))
|
|
||||||
grouped-elements))))}]
|
|
||||||
node))
|
|
||||||
|
|
||||||
(defn build-tree-root
|
|
||||||
"Builds the root level of the tree."
|
|
||||||
[segments separator]
|
|
||||||
(let [grouped-elements (sort-and-group-segments segments separator)]
|
|
||||||
(mapv (fn [[segment-name remaining-segments]]
|
|
||||||
(build-tree-node segment-name remaining-segments separator nil 0))
|
|
||||||
grouped-elements)))
|
|
||||||
|
|||||||
@ -112,8 +112,10 @@
|
|||||||
(:c2y params) (update-in [index :params :c2y] + (:c2y params)))
|
(:c2y params) (update-in [index :params :c2y] + (:c2y params)))
|
||||||
content))]
|
content))]
|
||||||
|
|
||||||
(impl/path-data
|
(if (some? modifiers)
|
||||||
(reduce apply-to-index (vec content) modifiers))))
|
(impl/path-data
|
||||||
|
(reduce apply-to-index (vec content) modifiers))
|
||||||
|
content)))
|
||||||
|
|
||||||
(defn transform-content
|
(defn transform-content
|
||||||
"Applies a transformation matrix over content and returns a new
|
"Applies a transformation matrix over content and returns a new
|
||||||
|
|||||||
@ -8,14 +8,10 @@ source ~/.bashrc
|
|||||||
|
|
||||||
echo "[start-tmux.sh] Installing node dependencies"
|
echo "[start-tmux.sh] Installing node dependencies"
|
||||||
pushd ~/penpot/frontend/
|
pushd ~/penpot/frontend/
|
||||||
corepack install;
|
./scripts/setup;
|
||||||
yarn install;
|
|
||||||
yarn playwright install chromium
|
|
||||||
popd
|
popd
|
||||||
pushd ~/penpot/exporter/
|
pushd ~/penpot/exporter/
|
||||||
corepack install;
|
./scripts/setup;
|
||||||
yarn install
|
|
||||||
yarn playwright install chromium
|
|
||||||
popd
|
popd
|
||||||
|
|
||||||
tmux -2 new-session -d -s penpot
|
tmux -2 new-session -d -s penpot
|
||||||
|
|||||||
8
exporter/scripts/setup
Executable file
8
exporter/scripts/setup
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e;
|
||||||
|
|
||||||
|
corepack enable;
|
||||||
|
corepack install;
|
||||||
|
yarn install;
|
||||||
|
yarn playwright install chromium
|
||||||
@ -305,7 +305,7 @@ test.describe("Inspect tab - Styles", () => {
|
|||||||
);
|
);
|
||||||
await openInspectTab(workspacePage);
|
await openInspectTab(workspacePage);
|
||||||
|
|
||||||
const panel = await getPanelByTitle(workspacePage, "Size & position");
|
const panel = await getPanelByTitle(workspacePage, "Size and position");
|
||||||
await expect(panel).toBeVisible();
|
await expect(panel).toBeVisible();
|
||||||
|
|
||||||
const propertyRow = panel.getByTestId("property-row");
|
const propertyRow = panel.getByTestId("property-row");
|
||||||
@ -335,7 +335,7 @@ test.describe("Inspect tab - Styles", () => {
|
|||||||
);
|
);
|
||||||
await openInspectTab(workspacePage);
|
await openInspectTab(workspacePage);
|
||||||
|
|
||||||
const panel = await getPanelByTitle(workspacePage, "Size & position");
|
const panel = await getPanelByTitle(workspacePage, "Size and position");
|
||||||
await expect(panel).toBeVisible();
|
await expect(panel).toBeVisible();
|
||||||
|
|
||||||
const propertyRow = panel.getByTestId("property-row");
|
const propertyRow = panel.getByTestId("property-row");
|
||||||
@ -375,7 +375,7 @@ test.describe("Inspect tab - Styles", () => {
|
|||||||
);
|
);
|
||||||
await openInspectTab(workspacePage);
|
await openInspectTab(workspacePage);
|
||||||
|
|
||||||
const panel = await getPanelByTitle(workspacePage, "Size & position");
|
const panel = await getPanelByTitle(workspacePage, "Size and position");
|
||||||
await expect(panel).toBeVisible();
|
await expect(panel).toBeVisible();
|
||||||
|
|
||||||
const propertyRow = panel.getByTestId("property-row");
|
const propertyRow = panel.getByTestId("property-row");
|
||||||
|
|||||||
@ -40,7 +40,6 @@ const setupEmptyTokensFile = async (page, options = {}) => {
|
|||||||
tokensUpdateCreateModal: workspacePage.tokensUpdateCreateModal,
|
tokensUpdateCreateModal: workspacePage.tokensUpdateCreateModal,
|
||||||
tokenThemesSetsSidebar: workspacePage.tokenThemesSetsSidebar,
|
tokenThemesSetsSidebar: workspacePage.tokenThemesSetsSidebar,
|
||||||
tokenSetItems: workspacePage.tokenSetItems,
|
tokenSetItems: workspacePage.tokenSetItems,
|
||||||
tokensSidebar: workspacePage.tokensSidebar,
|
|
||||||
tokenSetGroupItems: workspacePage.tokenSetGroupItems,
|
tokenSetGroupItems: workspacePage.tokenSetGroupItems,
|
||||||
tokenContextMenuForSet: workspacePage.tokenContextMenuForSet,
|
tokenContextMenuForSet: workspacePage.tokenContextMenuForSet,
|
||||||
};
|
};
|
||||||
@ -111,12 +110,15 @@ const checkInputFieldWithError = async (
|
|||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkInputFieldWithoutError = async (inputLocator) => {
|
const checkInputFieldWithoutError = async (
|
||||||
|
tokenThemeUpdateCreateModal,
|
||||||
|
inputLocator,
|
||||||
|
) => {
|
||||||
expect(await inputLocator.getAttribute("aria-invalid")).toBeNull();
|
expect(await inputLocator.getAttribute("aria-invalid")).toBeNull();
|
||||||
expect(await inputLocator.getAttribute("aria-describedby")).toBeNull();
|
expect(await inputLocator.getAttribute("aria-describedby")).toBeNull();
|
||||||
};
|
};
|
||||||
|
|
||||||
const testTokenCreationFlow = async (
|
async function testTokenCreationFlow(
|
||||||
page,
|
page,
|
||||||
{
|
{
|
||||||
tokenLabel,
|
tokenLabel,
|
||||||
@ -130,7 +132,7 @@ const testTokenCreationFlow = async (
|
|||||||
resolvedValueText,
|
resolvedValueText,
|
||||||
secondResolvedValueText,
|
secondResolvedValueText,
|
||||||
},
|
},
|
||||||
) => {
|
) {
|
||||||
const invalidValueError = "Invalid token value";
|
const invalidValueError = "Invalid token value";
|
||||||
const emptyNameError = "Name should be at least 1 character";
|
const emptyNameError = "Name should be at least 1 character";
|
||||||
const selfReferenceError = "Token has self reference";
|
const selfReferenceError = "Token has self reference";
|
||||||
@ -240,45 +242,7 @@ const testTokenCreationFlow = async (
|
|||||||
await expect(
|
await expect(
|
||||||
tokensTabPanel.getByRole("button", { name: "my-token-2" }),
|
tokensTabPanel.getByRole("button", { name: "my-token-2" }),
|
||||||
).toBeEnabled();
|
).toBeEnabled();
|
||||||
};
|
}
|
||||||
|
|
||||||
const unfoldTokenTree = async (tokensTabPanel, type, tokenName) => {
|
|
||||||
const tokenSegments = tokenName.split(".");
|
|
||||||
const tokenFolderTree = tokenSegments.slice(0, -1);
|
|
||||||
const tokenLeafName = tokenSegments.pop();
|
|
||||||
|
|
||||||
const typeParentWrapper = tokensTabPanel.getByTestId(`section-${type}`);
|
|
||||||
const typeSectionButton = typeParentWrapper
|
|
||||||
.getByRole("button", {
|
|
||||||
name: type,
|
|
||||||
})
|
|
||||||
.first();
|
|
||||||
|
|
||||||
const isSectionExpanded =
|
|
||||||
await typeSectionButton.getAttribute("aria-expanded");
|
|
||||||
|
|
||||||
if (isSectionExpanded === "false") {
|
|
||||||
await typeSectionButton.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const segment of tokenFolderTree) {
|
|
||||||
const segmentButton = typeParentWrapper
|
|
||||||
.getByRole("listitem")
|
|
||||||
.getByRole("button", { name: segment })
|
|
||||||
.first();
|
|
||||||
|
|
||||||
const isExpanded = await segmentButton.getAttribute("aria-expanded");
|
|
||||||
if (isExpanded === "false") {
|
|
||||||
await segmentButton.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
typeParentWrapper.getByRole("button", {
|
|
||||||
name: tokenLeafName,
|
|
||||||
}),
|
|
||||||
).toBeEnabled();
|
|
||||||
};
|
|
||||||
|
|
||||||
test.describe("Tokens: Tokens Tab", () => {
|
test.describe("Tokens: Tokens Tab", () => {
|
||||||
test("Clicking tokens tab button opens tokens sidebar tab", async ({
|
test("Clicking tokens tab button opens tokens sidebar tab", async ({
|
||||||
@ -434,12 +398,15 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
const emptyNameError = "Name should be at least 1 character";
|
const emptyNameError = "Name should be at least 1 character";
|
||||||
const selfReferenceError = "Token has self reference";
|
const selfReferenceError = "Token has self reference";
|
||||||
const missingReferenceError = "Missing token references";
|
const missingReferenceError = "Missing token references";
|
||||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
|
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
||||||
await setupEmptyTokensFile(page);
|
await setupEmptyTokensFile(page);
|
||||||
|
|
||||||
await tokensSidebar
|
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||||
.getByRole("button", { name: "Add Token: Color" })
|
const addTokenButton = tokensTabPanel.getByRole("button", {
|
||||||
.click();
|
name: `Add Token: Color`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await addTokenButton.click();
|
||||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
// Placeholder checks
|
// Placeholder checks
|
||||||
@ -504,34 +471,38 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
await expect(submitButton).toBeEnabled();
|
await expect(submitButton).toBeEnabled();
|
||||||
await submitButton.click();
|
await submitButton.click();
|
||||||
|
|
||||||
await unfoldTokenTree(tokensSidebar, "color", "color.primary");
|
await expect(
|
||||||
|
tokensTabPanel.getByRole("button", {
|
||||||
|
name: "color.primary",
|
||||||
|
}),
|
||||||
|
).toBeEnabled();
|
||||||
|
|
||||||
// Create token referencing the previous one with keyboard
|
// Create token referencing the previous one with keyboard
|
||||||
|
|
||||||
await tokensSidebar
|
await tokensTabPanel
|
||||||
.getByRole("button", { name: "Add Token: Color" })
|
.getByRole("button", { name: "Add Token: Color" })
|
||||||
.click();
|
.click();
|
||||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
await nameField.click();
|
await nameField.click();
|
||||||
await nameField.fill("secondary");
|
await nameField.fill("color.secondary");
|
||||||
await nameField.press("Tab");
|
await nameField.press("Tab");
|
||||||
|
|
||||||
await valueField.click();
|
await valueField.click();
|
||||||
await valueField.fill("{color.primary}");
|
await valueField.fill("{color.primary}");
|
||||||
|
|
||||||
await expect(submitButton).toBeEnabled();
|
await expect(submitButton).toBeEnabled();
|
||||||
await submitButton.press("Enter");
|
await nameField.press("Enter");
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
tokensSidebar.getByRole("button", {
|
tokensTabPanel.getByRole("button", {
|
||||||
name: "secondary",
|
name: "color.secondary",
|
||||||
}),
|
}),
|
||||||
).toBeEnabled();
|
).toBeEnabled();
|
||||||
|
|
||||||
// Tokens tab panel should have two tokens with the color red / #ff0000
|
// Tokens tab panel should have two tokens with the color red / #ff0000
|
||||||
await expect(
|
await expect(
|
||||||
tokensSidebar.getByRole("button", { name: "#ff0000" }),
|
tokensTabPanel.getByRole("button", { name: "#ff0000" }),
|
||||||
).toHaveCount(2);
|
).toHaveCount(2);
|
||||||
|
|
||||||
// Global set has been auto created and is active
|
// Global set has been auto created and is active
|
||||||
@ -547,7 +518,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
).toHaveAttribute("aria-checked", "true");
|
).toHaveAttribute("aria-checked", "true");
|
||||||
|
|
||||||
// Check color picker
|
// Check color picker
|
||||||
await tokensSidebar
|
await tokensTabPanel
|
||||||
.getByRole("button", { name: "Add Token: Color" })
|
.getByRole("button", { name: "Add Token: Color" })
|
||||||
.click();
|
.click();
|
||||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
@ -1108,7 +1079,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
const emptyNameError = "Name should be at least 1 character";
|
const emptyNameError = "Name should be at least 1 character";
|
||||||
|
|
||||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
||||||
await setupEmptyTokensFile(page, { flags: ["enable-token-shadow"] });
|
await setupEmptyTokensFile(page, {flags: ["enable-token-shadow"]});
|
||||||
|
|
||||||
// Open modal
|
// Open modal
|
||||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||||
@ -1536,15 +1507,24 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
test("User edits token and auto created set show up in the sidebar", async ({
|
test("User edits token and auto created set show up in the sidebar", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
const { tokensUpdateCreateModal, tokensSidebar, tokenContextMenuForToken } =
|
const {
|
||||||
await setupTokensFile(page);
|
workspacePage,
|
||||||
|
tokensUpdateCreateModal,
|
||||||
|
tokenThemesSetsSidebar,
|
||||||
|
tokensSidebar,
|
||||||
|
tokenContextMenuForToken,
|
||||||
|
} = await setupTokensFile(page);
|
||||||
|
|
||||||
await expect(tokensSidebar).toBeVisible();
|
await expect(tokensSidebar).toBeVisible();
|
||||||
|
|
||||||
await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
const tokensColorGroup = tokensSidebar.getByRole("button", {
|
||||||
|
name: "Color 92",
|
||||||
|
});
|
||||||
|
await expect(tokensColorGroup).toBeVisible();
|
||||||
|
await tokensColorGroup.click();
|
||||||
|
|
||||||
const colorToken = tokensSidebar.getByRole("button", {
|
const colorToken = tokensSidebar.getByRole("button", {
|
||||||
name: "100",
|
name: "colors.blue.100",
|
||||||
});
|
});
|
||||||
await expect(colorToken).toBeVisible();
|
await expect(colorToken).toBeVisible();
|
||||||
await colorToken.click({ button: "right" });
|
await colorToken.click({ button: "right" });
|
||||||
@ -1561,10 +1541,8 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
|
|
||||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||||
|
|
||||||
await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100.changed");
|
|
||||||
|
|
||||||
const colorTokenChanged = tokensSidebar.getByRole("button", {
|
const colorTokenChanged = tokensSidebar.getByRole("button", {
|
||||||
name: "changed",
|
name: "colors.blue.100.changed",
|
||||||
});
|
});
|
||||||
await expect(colorTokenChanged).toBeVisible();
|
await expect(colorTokenChanged).toBeVisible();
|
||||||
});
|
});
|
||||||
@ -1655,10 +1633,11 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("User creates grouped color token", async ({ page }) => {
|
test("User creates grouped color token", async ({ page }) => {
|
||||||
const { workspacePage, tokensUpdateCreateModal, tokensSidebar } =
|
const { workspacePage, tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
||||||
await setupEmptyTokensFile(page);
|
await setupEmptyTokensFile(page);
|
||||||
|
|
||||||
await tokensSidebar
|
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||||
|
await tokensTabPanel
|
||||||
.getByRole("button", { name: "Add Token: Color" })
|
.getByRole("button", { name: "Add Token: Color" })
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
@ -1670,7 +1649,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||||
|
|
||||||
await nameField.click();
|
await nameField.click();
|
||||||
await nameField.fill("dark.primary");
|
await nameField.fill("color.dark.primary");
|
||||||
|
|
||||||
await valueField.click();
|
await valueField.click();
|
||||||
await valueField.fill("red");
|
await valueField.fill("red");
|
||||||
@ -1681,9 +1660,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
await expect(submitButton).toBeEnabled();
|
await expect(submitButton).toBeEnabled();
|
||||||
await submitButton.click();
|
await submitButton.click();
|
||||||
|
|
||||||
await unfoldTokenTree(tokensSidebar, "color", "dark.primary");
|
await expect(tokensTabPanel.getByLabel("color.dark.primary")).toBeEnabled();
|
||||||
|
|
||||||
await expect(tokensSidebar.getByLabel("primary")).toBeEnabled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("User cant create regular token with value missing", async ({
|
test("User cant create regular token with value missing", async ({
|
||||||
@ -1699,6 +1676,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
name: "Save",
|
name: "Save",
|
||||||
});
|
});
|
||||||
@ -1708,7 +1686,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
|
|
||||||
// Fill in name but leave value empty
|
// Fill in name but leave value empty
|
||||||
await nameField.click();
|
await nameField.click();
|
||||||
await nameField.fill("primary");
|
await nameField.fill("color.primary");
|
||||||
|
|
||||||
// Submit button should remain disabled when value is empty
|
// Submit button should remain disabled when value is empty
|
||||||
await expect(submitButton).toBeDisabled();
|
await expect(submitButton).toBeDisabled();
|
||||||
@ -1726,6 +1704,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
.click();
|
.click();
|
||||||
|
|
||||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||||
|
|
||||||
await valueField.click();
|
await valueField.click();
|
||||||
@ -1775,10 +1754,15 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
|
|
||||||
await expect(tokensSidebar).toBeVisible();
|
await expect(tokensSidebar).toBeVisible();
|
||||||
|
|
||||||
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
const tokensColorGroup = tokensSidebar.getByRole("button", {
|
||||||
|
name: "Color 92",
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(tokensColorGroup).toBeVisible();
|
||||||
|
await tokensColorGroup.click();
|
||||||
|
|
||||||
const colorToken = tokensSidebar.getByRole("button", {
|
const colorToken = tokensSidebar.getByRole("button", {
|
||||||
name: "100",
|
name: "colors.blue.100",
|
||||||
});
|
});
|
||||||
|
|
||||||
await colorToken.click({ button: "right" });
|
await colorToken.click({ button: "right" });
|
||||||
@ -1798,10 +1782,15 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
|
|
||||||
await expect(tokensSidebar).toBeVisible();
|
await expect(tokensSidebar).toBeVisible();
|
||||||
|
|
||||||
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
const tokensColorGroup = tokensSidebar.getByRole("button", {
|
||||||
|
name: "Color 92",
|
||||||
|
});
|
||||||
|
await expect(tokensColorGroup).toBeVisible();
|
||||||
|
|
||||||
|
await tokensColorGroup.click();
|
||||||
|
|
||||||
const colorToken = tokensSidebar.getByRole("button", {
|
const colorToken = tokensSidebar.getByRole("button", {
|
||||||
name: "100",
|
name: "colors.blue.100",
|
||||||
});
|
});
|
||||||
await expect(colorToken).toBeVisible();
|
await expect(colorToken).toBeVisible();
|
||||||
await colorToken.click({ button: "right" });
|
await colorToken.click({ button: "right" });
|
||||||
@ -1814,7 +1803,8 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("User fold/unfold color tokens", async ({ page }) => {
|
test("User fold/unfold color tokens", async ({ page }) => {
|
||||||
const { tokensSidebar } = await setupTokensFile(page);
|
const { tokensSidebar, tokenContextMenuForToken } =
|
||||||
|
await setupTokensFile(page);
|
||||||
|
|
||||||
await expect(tokensSidebar).toBeVisible();
|
await expect(tokensSidebar).toBeVisible();
|
||||||
|
|
||||||
@ -1824,10 +1814,8 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
await expect(tokensColorGroup).toBeVisible();
|
await expect(tokensColorGroup).toBeVisible();
|
||||||
await tokensColorGroup.click();
|
await tokensColorGroup.click();
|
||||||
|
|
||||||
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
|
||||||
|
|
||||||
const colorToken = tokensSidebar.getByRole("button", {
|
const colorToken = tokensSidebar.getByRole("button", {
|
||||||
name: "100",
|
name: "colors.blue.100",
|
||||||
});
|
});
|
||||||
await expect(colorToken).toBeVisible();
|
await expect(colorToken).toBeVisible();
|
||||||
await tokensColorGroup.click();
|
await tokensColorGroup.click();
|
||||||
@ -2230,10 +2218,13 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||||
await tokensTabButton.click();
|
await tokensTabButton.click();
|
||||||
|
|
||||||
unfoldTokenTree(tokensSidebar, "color", "colors.black");
|
await tokensSidebar
|
||||||
|
.getByRole("button")
|
||||||
|
.filter({ hasText: "Color" })
|
||||||
|
.click();
|
||||||
|
|
||||||
await tokensSidebar
|
await tokensSidebar
|
||||||
.getByRole("button", { name: "black" })
|
.getByRole("button", { name: "colors.black" })
|
||||||
.click({ button: "right" });
|
.click({ button: "right" });
|
||||||
await tokenContextMenuForToken.getByText("Fill").click();
|
await tokenContextMenuForToken.getByText("Fill").click();
|
||||||
|
|
||||||
@ -2471,7 +2462,7 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
await nameField.fill("primary");
|
await nameField.fill("shadow.primary");
|
||||||
|
|
||||||
// User adds first shadow with a color from the color ramp
|
// User adds first shadow with a color from the color ramp
|
||||||
const firstShadowFields = tokensUpdateCreateModal.getByTestId(
|
const firstShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||||
@ -2718,11 +2709,9 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
await submitButton.click();
|
await submitButton.click();
|
||||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||||
|
|
||||||
unfoldTokenTree(tokensSidebar, "shadow", "primary");
|
|
||||||
|
|
||||||
// Verify token appears in sidebar
|
// Verify token appears in sidebar
|
||||||
const shadowToken = tokensSidebar.getByRole("button", {
|
const shadowToken = tokensSidebar.getByRole("button", {
|
||||||
name: "primary",
|
name: "shadow.primary",
|
||||||
});
|
});
|
||||||
await expect(shadowToken).toBeEnabled();
|
await expect(shadowToken).toBeEnabled();
|
||||||
|
|
||||||
|
|||||||
6
frontend/scripts/setup
Executable file
6
frontend/scripts/setup
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
corepack enable;
|
||||||
|
corepack install;
|
||||||
|
yarn install;
|
||||||
|
yarn playwright install chromium;
|
||||||
@ -1,10 +1,10 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -ex
|
SCRIPT_DIR=$(dirname $0);
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
set -ex
|
||||||
yarn install;
|
|
||||||
|
$SCRIPT_DIR/setup;
|
||||||
|
|
||||||
yarn run playwright install chromium --with-deps;
|
|
||||||
yarn run build:storybook
|
yarn run build:storybook
|
||||||
yarn run test:storybook
|
yarn run test:storybook
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(dirname $0);
|
||||||
|
|
||||||
set -ex
|
set -ex
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
$SCRIPT_DIR/setup;
|
||||||
yarn install;
|
|
||||||
yarn run playwright install chromium --with-deps;
|
|
||||||
yarn run test:e2e -x --workers=2 --reporter=list "$@";
|
yarn run test:e2e -x --workers=2 --reporter=list "$@";
|
||||||
|
|||||||
@ -47,32 +47,31 @@
|
|||||||
(ptk/reify ::apply-content-modifiers
|
(ptk/reify ::apply-content-modifiers
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [it state _]
|
(watch [it state _]
|
||||||
(let [page-id (get state :current-page-id state)
|
(let [id (st/get-path-id state)
|
||||||
objects (dsh/lookup-page-objects state)
|
shape (st/get-path state)
|
||||||
|
|
||||||
id (st/get-path-id state)
|
|
||||||
|
|
||||||
shape
|
|
||||||
(st/get-path state)
|
|
||||||
|
|
||||||
content-modifiers
|
content-modifiers
|
||||||
(dm/get-in state [:workspace-local :edit-path id :content-modifiers])
|
(dm/get-in state [:workspace-local :edit-path id :content-modifiers])]
|
||||||
|
(if (or (nil? shape) (nil? content-modifiers))
|
||||||
|
(rx/of (dwe/clear-edition-mode))
|
||||||
|
(let [page-id (get state :current-page-id state)
|
||||||
|
objects (dsh/lookup-page-objects state)
|
||||||
|
|
||||||
content (get shape :content)
|
content (get shape :content)
|
||||||
new-content (path/apply-content-modifiers content content-modifiers)
|
new-content (path/apply-content-modifiers content content-modifiers)
|
||||||
|
|
||||||
old-points (path.segment/get-points content)
|
old-points (path.segment/get-points content)
|
||||||
new-points (path.segment/get-points new-content)
|
new-points (path.segment/get-points new-content)
|
||||||
point-change (->> (map hash-map old-points new-points) (reduce merge))]
|
point-change (->> (map hash-map old-points new-points) (reduce merge))]
|
||||||
|
|
||||||
(when (and (some? new-content) (some? shape))
|
(when (and (some? new-content) (some? shape))
|
||||||
(let [changes (changes/generate-path-changes it objects page-id shape (:content shape) new-content)]
|
(let [changes (changes/generate-path-changes it objects page-id shape (:content shape) new-content)]
|
||||||
(if (empty? new-content)
|
(if (empty? new-content)
|
||||||
(rx/of (dch/commit-changes changes)
|
(rx/of (dch/commit-changes changes)
|
||||||
(dwe/clear-edition-mode))
|
(dwe/clear-edition-mode))
|
||||||
(rx/of (dch/commit-changes changes)
|
(rx/of (dch/commit-changes changes)
|
||||||
(selection/update-selection point-change)
|
(selection/update-selection point-change)
|
||||||
(fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers :moving-nodes :moving-handler))))))))))
|
(fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers :moving-nodes :moving-handler))))))))))))
|
||||||
|
|
||||||
(defn modify-content-point
|
(defn modify-content-point
|
||||||
[content {dx :x dy :y} modifiers point]
|
[content {dx :x dy :y} modifiers point]
|
||||||
|
|||||||
@ -53,6 +53,6 @@
|
|||||||
|
|
||||||
|
|
||||||
(mf/defc inspect-title-bar*
|
(mf/defc inspect-title-bar*
|
||||||
[{:keys [class title]}]
|
[{:keys [class title title-class]}]
|
||||||
[:div {:class [(stl/css :title-bar) class]}
|
[:div {:class [(stl/css :title-bar) class]}
|
||||||
[:div {:class (stl/css :title-only :inspect-title)} title]])
|
[:div {:class [title-class (stl/css :title-only :inspect-title)]} title]])
|
||||||
|
|||||||
@ -1,49 +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.main.ui.ds.layers.layer-button
|
|
||||||
(:require-macros
|
|
||||||
[app.main.style :as stl])
|
|
||||||
(:require
|
|
||||||
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
|
|
||||||
[rumext.v2 :as mf]))
|
|
||||||
|
|
||||||
(def ^:private schema:layer-button
|
|
||||||
[:map
|
|
||||||
[:label :string]
|
|
||||||
[:description {:optional true} [:maybe :string]]
|
|
||||||
[:class {:optional true} :string]
|
|
||||||
[:expandable {:optional true} :boolean]
|
|
||||||
[:expanded {:optional true} :boolean]
|
|
||||||
[:icon {:optional true} :string]
|
|
||||||
[:on-toggle-expand fn?]])
|
|
||||||
|
|
||||||
(mf/defc layer-button*
|
|
||||||
{::mf/schema schema:layer-button}
|
|
||||||
[{:keys [label description class is-expandable expanded icon on-toggle-expand children] :rest props}]
|
|
||||||
(let [button-props (mf/spread-props props
|
|
||||||
{:class [class (stl/css-case :layer-button true
|
|
||||||
:layer-button--expandable is-expandable
|
|
||||||
:layer-button--expanded expanded)]
|
|
||||||
:type "button"
|
|
||||||
:on-click on-toggle-expand})]
|
|
||||||
[:div {:class (stl/css :layer-button-wrapper)}
|
|
||||||
[:> "button" button-props
|
|
||||||
[:div {:class (stl/css :layer-button-content)}
|
|
||||||
(when is-expandable
|
|
||||||
(if expanded
|
|
||||||
[:> icon* {:icon-id i/arrow-down :class (stl/css :folder-node-icon)}]
|
|
||||||
[:> icon* {:icon-id i/arrow-right :class (stl/css :folder-node-icon)}]))
|
|
||||||
(when icon
|
|
||||||
[:> icon* {:icon-id icon :class (stl/css :layer-button-icon)}])
|
|
||||||
[:span {:class (stl/css :layer-button-name)}
|
|
||||||
label]
|
|
||||||
(when description
|
|
||||||
[:span {:class (stl/css :layer-button-description)}
|
|
||||||
description])
|
|
||||||
[:span {:class (stl/css :layer-button-quantity)}]]]
|
|
||||||
[:div {:class (stl/css :layer-button-actions)}
|
|
||||||
children]]))
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
//
|
|
||||||
// Copyright (c) KALEIDOS INC
|
|
||||||
|
|
||||||
@use "ds/_borders.scss" as *;
|
|
||||||
@use "ds/_sizes.scss" as *;
|
|
||||||
@use "ds/typography.scss" as *;
|
|
||||||
@use "ds/colors.scss" as *;
|
|
||||||
|
|
||||||
.layer-button-wrapper {
|
|
||||||
--layer-button-block-size: #{$sz-32};
|
|
||||||
--layer-button-background: var(--color-background-primary);
|
|
||||||
--layer-button-text: var(--color-foreground-secondary);
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
block-size: var(--layer-button-block-size);
|
|
||||||
|
|
||||||
background: var(--layer-button-background);
|
|
||||||
color: var(--layer-button-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.layer-button {
|
|
||||||
@include use-typography("body-small");
|
|
||||||
|
|
||||||
appearance: none;
|
|
||||||
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layer-button--expanded {
|
|
||||||
& .layer-button-name {
|
|
||||||
color: var(--color-foreground-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.layer-button-content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--sp-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.layer-button-description {
|
|
||||||
padding: var(--sp-xs);
|
|
||||||
background-color: var(--color-background-tertiary);
|
|
||||||
border-radius: $br-6;
|
|
||||||
}
|
|
||||||
@ -12,14 +12,17 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--sp-l);
|
gap: var(--sp-l);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100vh - px2rem(128)); // TODO: Fix this hardcoded value
|
max-height: calc(100vh - px2rem(128)); // TODO: Fix this hardcoded value
|
||||||
padding-top: var(--sp-s);
|
padding-top: var(--sp-s);
|
||||||
|
padding-inline: var(--sp-m);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
|
background-color: var(--low-emphasis-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-element-options {
|
.workspace-element-options {
|
||||||
height: calc(100vh - px2rem(200)); // TODO: Fix this hardcoded value
|
max-height: calc(100vh - px2rem(180)); // TODO: Fix this hardcoded value
|
||||||
padding-inline: var(--sp-m);
|
padding-inline: var(--sp-m);
|
||||||
|
background-color: var(--low-emphasis-background);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,8 @@
|
|||||||
[:div {:class (stl/css :attributes-block)}
|
[:div {:class (stl/css :attributes-block)}
|
||||||
[:> inspect-title-bar*
|
[:> inspect-title-bar*
|
||||||
{:title (tr "inspect.attributes.blur")
|
{:title (tr "inspect.attributes.blur")
|
||||||
:class (stl/css :title-spacing-blur)}
|
:class (stl/css :title-wrapper)
|
||||||
|
:title-class (stl/css :blur-attr-title)}
|
||||||
(when (= (count shapes) 1)
|
(when (= (count shapes) 1)
|
||||||
[:> copy-button* {:data (css/get-css-property objects (first shapes) :filter)
|
[:> copy-button* {:data (css/get-css-property objects (first shapes) :filter)
|
||||||
:class (stl/css :copy-btn-title)}])]
|
:class (stl/css :copy-btn-title)}])]
|
||||||
|
|||||||
@ -5,17 +5,28 @@
|
|||||||
// Copyright (c) KALEIDOS INC
|
// Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
@use "refactor/common-refactor.scss" as deprecated;
|
@use "refactor/common-refactor.scss" as deprecated;
|
||||||
|
@use "ds/_borders.scss" as *;
|
||||||
|
@use "ds/_sizes.scss" as *;
|
||||||
|
|
||||||
.attributes-block {
|
.attributes-block {
|
||||||
@include deprecated.flexColumn;
|
--box-border-color: var(--color-background-primary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-block-end: $b-2 solid var(--box-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-spacing-blur {
|
.title-wrapper {
|
||||||
@extend .attr-title;
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blur-attr-title {
|
||||||
|
color: var(--entry-foreground-color-hover);
|
||||||
|
padding-block: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.blur-row {
|
.blur-row {
|
||||||
@extend .attr-row;
|
@extend .attr-row;
|
||||||
|
block-size: $sz-36;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-children {
|
.button-children {
|
||||||
@ -23,5 +34,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.copy-btn-title {
|
.copy-btn-title {
|
||||||
max-width: deprecated.$s-28;
|
max-inline-size: $sz-28;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -68,7 +68,8 @@
|
|||||||
[:div {:class (stl/css :attributes-block)}
|
[:div {:class (stl/css :attributes-block)}
|
||||||
[:> inspect-title-bar*
|
[:> inspect-title-bar*
|
||||||
{:title (tr "inspect.attributes.fill")
|
{:title (tr "inspect.attributes.fill")
|
||||||
:class (stl/css :title-spacing-fill)}]
|
:class (stl/css :title-wrapper)
|
||||||
|
:class-title (stl/css :fill-attr-title)}]
|
||||||
|
|
||||||
[:div {:class (stl/css :attributes-content)}
|
[:div {:class (stl/css :attributes-content)}
|
||||||
(for [shape shapes]
|
(for [shape shapes]
|
||||||
|
|||||||
@ -5,16 +5,30 @@
|
|||||||
// Copyright (c) KALEIDOS INC
|
// Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
@use "refactor/common-refactor.scss" as deprecated;
|
@use "refactor/common-refactor.scss" as deprecated;
|
||||||
|
@use "ds/_borders.scss" as *;
|
||||||
|
@use "ds/_sizes.scss" as *;
|
||||||
|
|
||||||
.attributes-block {
|
.attributes-block {
|
||||||
@include deprecated.flexColumn;
|
--box-border-color: var(--color-background-primary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-block-end: $b-2 solid var(--box-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-spacing-fill {
|
.title-wrapper {
|
||||||
@extend .attr-title;
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fill-attr-title {
|
||||||
|
color: var(--entry-foreground-color-hover);
|
||||||
|
padding-block: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.attributes-content {
|
.attributes-content {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: deprecated.$s-4;
|
gap: deprecated.$s-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.attributes-fill-block {
|
||||||
|
block-size: $sz-36;
|
||||||
|
}
|
||||||
|
|||||||
@ -44,7 +44,8 @@
|
|||||||
[:div {:class (stl/css :attributes-block)}
|
[:div {:class (stl/css :attributes-block)}
|
||||||
[:> inspect-title-bar*
|
[:> inspect-title-bar*
|
||||||
{:title (tr "inspect.attributes.size")
|
{:title (tr "inspect.attributes.size")
|
||||||
:class (stl/css :title-spacing-geometry)}
|
:class (stl/css :title-wrapper)
|
||||||
|
:title-class (stl/css :geometry-attr-title)}
|
||||||
|
|
||||||
(when (= (count shapes) 1)
|
(when (= (count shapes) 1)
|
||||||
[:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties)
|
[:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties)
|
||||||
|
|||||||
@ -5,17 +5,28 @@
|
|||||||
// Copyright (c) KALEIDOS INC
|
// Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
@use "refactor/common-refactor.scss" as deprecated;
|
@use "refactor/common-refactor.scss" as deprecated;
|
||||||
|
@use "ds/_borders.scss" as *;
|
||||||
|
@use "ds/_sizes.scss" as *;
|
||||||
|
|
||||||
.attributes-block {
|
.attributes-block {
|
||||||
@include deprecated.flexColumn;
|
--box-border-color: var(--color-background-primary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-block-end: $b-2 solid var(--box-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-spacing-geometry {
|
.title-wrapper {
|
||||||
@extend .attr-title;
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.geometry-attr-title {
|
||||||
|
color: var(--entry-foreground-color-hover);
|
||||||
|
padding-block: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.geometry-row {
|
.geometry-row {
|
||||||
@extend .attr-row;
|
@extend .attr-row;
|
||||||
|
block-size: $sz-36;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-children {
|
.button-children {
|
||||||
@ -23,5 +34,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.copy-btn-title {
|
.copy-btn-title {
|
||||||
max-width: deprecated.$s-28;
|
max-inline-size: $sz-28;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,7 +57,8 @@
|
|||||||
[:div {:class (stl/css :attributes-block)}
|
[:div {:class (stl/css :attributes-block)}
|
||||||
[:> inspect-title-bar*
|
[:> inspect-title-bar*
|
||||||
{:title "Layout"
|
{:title "Layout"
|
||||||
:class (stl/css :title-spacing-layout)}
|
:class (stl/css :title-wrapper)
|
||||||
|
:title-class (stl/css :layout-attr-title)}
|
||||||
|
|
||||||
(when (= (count shapes) 1)
|
(when (= (count shapes) 1)
|
||||||
[:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties)
|
[:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties)
|
||||||
|
|||||||
@ -5,17 +5,28 @@
|
|||||||
// Copyright (c) KALEIDOS INC
|
// Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
@use "refactor/common-refactor.scss" as deprecated;
|
@use "refactor/common-refactor.scss" as deprecated;
|
||||||
|
@use "ds/_borders.scss" as *;
|
||||||
|
@use "ds/_sizes.scss" as *;
|
||||||
|
|
||||||
.attributes-block {
|
.attributes-block {
|
||||||
@include deprecated.flexColumn;
|
--box-border-color: var(--color-background-primary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-block-end: $b-2 solid var(--box-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-spacing-layout {
|
.title-wrapper {
|
||||||
@extend .attr-title;
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-attr-title {
|
||||||
|
color: var(--entry-foreground-color-hover);
|
||||||
|
padding-block: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-row {
|
.layout-row {
|
||||||
@extend .attr-row;
|
@extend .attr-row;
|
||||||
|
block-size: $sz-36;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-children {
|
.button-children {
|
||||||
@ -23,5 +34,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.copy-btn-title {
|
.copy-btn-title {
|
||||||
max-width: deprecated.$s-28;
|
max-inline-size: $sz-28;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,7 +69,8 @@
|
|||||||
[:div {:class (stl/css :attributes-block)}
|
[:div {:class (stl/css :attributes-block)}
|
||||||
[:> title-bar* {:collapsable false
|
[:> title-bar* {:collapsable false
|
||||||
:title menu-title
|
:title menu-title
|
||||||
:class (stl/css :title-spacing-layout-element)}
|
:class (stl/css :title-wrapper)
|
||||||
|
:title-class (stl/css :layout-element-attr-title)}
|
||||||
(when (= (count shapes) 1)
|
(when (= (count shapes) 1)
|
||||||
[:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties)
|
[:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties)
|
||||||
:class (stl/css :copy-btn-title)}])]
|
:class (stl/css :copy-btn-title)}])]
|
||||||
|
|||||||
@ -5,17 +5,28 @@
|
|||||||
// Copyright (c) KALEIDOS INC
|
// Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
@use "refactor/common-refactor.scss" as deprecated;
|
@use "refactor/common-refactor.scss" as deprecated;
|
||||||
|
@use "ds/_borders.scss" as *;
|
||||||
|
@use "ds/_sizes.scss" as *;
|
||||||
|
|
||||||
.attributes-block {
|
.attributes-block {
|
||||||
@include deprecated.flexColumn;
|
--box-border-color: var(--color-background-primary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-block-end: $b-2 solid var(--box-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-spacing-layout-element {
|
.title-wrapper {
|
||||||
@extend .attr-title;
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-element-attr-title {
|
||||||
|
color: var(--entry-foreground-color-hover);
|
||||||
|
padding-block: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-element-row {
|
.layout-element-row {
|
||||||
@extend .attr-row;
|
@extend .attr-row;
|
||||||
|
block-size: $sz-36;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-children {
|
.button-children {
|
||||||
@ -23,5 +34,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.copy-btn-title {
|
.copy-btn-title {
|
||||||
max-width: deprecated.$s-28;
|
max-inline-size: $sz-28;
|
||||||
|
max-inline-size: $sz-28;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,7 +63,8 @@
|
|||||||
[:div {:class (stl/css :attributes-block)}
|
[:div {:class (stl/css :attributes-block)}
|
||||||
[:> inspect-title-bar*
|
[:> inspect-title-bar*
|
||||||
{:title (tr "inspect.attributes.shadow")
|
{:title (tr "inspect.attributes.shadow")
|
||||||
:class (stl/css :title-spacing-shadow)}]
|
:class (stl/css :title-wrapper)
|
||||||
|
:title-class (stl/css :shadow-attr-title)}]
|
||||||
|
|
||||||
[:div {:class (stl/css :attributes-content)}
|
[:div {:class (stl/css :attributes-content)}
|
||||||
(for [shape shapes]
|
(for [shape shapes]
|
||||||
|
|||||||
@ -5,17 +5,28 @@
|
|||||||
// Copyright (c) KALEIDOS INC
|
// Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
@use "refactor/common-refactor.scss" as deprecated;
|
@use "refactor/common-refactor.scss" as deprecated;
|
||||||
|
@use "ds/_borders.scss" as *;
|
||||||
|
@use "ds/_sizes.scss" as *;
|
||||||
|
|
||||||
.attributes-block {
|
.attributes-block {
|
||||||
@include deprecated.flexColumn;
|
--box-border-color: var(--color-background-primary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-block-end: $b-2 solid var(--box-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-spacing-shadow {
|
.title-wrapper {
|
||||||
@extend .attr-title;
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadow-attr-title {
|
||||||
|
color: var(--entry-foreground-color-hover);
|
||||||
|
padding-block: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.shadow-row {
|
.shadow-row {
|
||||||
@extend .attr-row;
|
@extend .attr-row;
|
||||||
|
block-size: $sz-36;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-children {
|
.button-children {
|
||||||
|
|||||||
@ -88,7 +88,8 @@
|
|||||||
[:div {:class (stl/css :attributes-block)}
|
[:div {:class (stl/css :attributes-block)}
|
||||||
[:> inspect-title-bar*
|
[:> inspect-title-bar*
|
||||||
{:title (tr "inspect.attributes.stroke")
|
{:title (tr "inspect.attributes.stroke")
|
||||||
:class (stl/css :title-spacing-stroke)}]
|
:class (stl/css :title-wrapper)
|
||||||
|
:title-class (stl/css :stroke-attr-title)}]
|
||||||
|
|
||||||
[:div {:class (stl/css :attributes-content)}
|
[:div {:class (stl/css :attributes-content)}
|
||||||
(for [shape shapes]
|
(for [shape shapes]
|
||||||
|
|||||||
@ -5,21 +5,34 @@
|
|||||||
// Copyright (c) KALEIDOS INC
|
// Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
@use "refactor/common-refactor.scss" as deprecated;
|
@use "refactor/common-refactor.scss" as deprecated;
|
||||||
|
@use "ds/_borders.scss" as *;
|
||||||
|
@use "ds/_sizes.scss" as *;
|
||||||
|
|
||||||
.attributes-block {
|
.attributes-block {
|
||||||
@include deprecated.flexColumn;
|
--box-border-color: var(--color-background-primary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-block-end: $b-2 solid var(--box-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-spacing-stroke {
|
.title-wrapper {
|
||||||
@extend .attr-title;
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stroke-attr-title {
|
||||||
|
color: var(--entry-foreground-color-hover);
|
||||||
|
padding-block: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.attributes-stroke-block {
|
.attributes-stroke-block {
|
||||||
@include deprecated.flexColumn;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--sp-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stroke-row {
|
.stroke-row {
|
||||||
@extend .attr-row;
|
@extend .attr-row;
|
||||||
|
block-size: $sz-36;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-children {
|
.button-children {
|
||||||
@ -28,5 +41,5 @@
|
|||||||
|
|
||||||
.attributes-content {
|
.attributes-content {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: deprecated.$s-4;
|
gap: var(--sp-xs);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,5 +54,6 @@
|
|||||||
[:div {:class (stl/css :attributes-block)}
|
[:div {:class (stl/css :attributes-block)}
|
||||||
[:> inspect-title-bar*
|
[:> inspect-title-bar*
|
||||||
{:title (tr "workspace.sidebar.options.svg-attrs.title")
|
{:title (tr "workspace.sidebar.options.svg-attrs.title")
|
||||||
:class (stl/css :title-spacing-svg)}]
|
:class (stl/css :title-wrapper)
|
||||||
|
:title-class (stl/css :svg-attr-title)}]
|
||||||
[:& svg-block {:shape shape}]])))
|
[:& svg-block {:shape shape}]])))
|
||||||
|
|||||||
@ -5,17 +5,29 @@
|
|||||||
// Copyright (c) KALEIDOS INC
|
// Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
@use "refactor/common-refactor.scss" as deprecated;
|
@use "refactor/common-refactor.scss" as deprecated;
|
||||||
|
@use "ds/_borders.scss" as *;
|
||||||
|
@use "ds/_sizes.scss" as *;
|
||||||
|
@use "ds/typography.scss" as *;
|
||||||
|
|
||||||
.attributes-block {
|
.attributes-block {
|
||||||
@include deprecated.flexColumn;
|
--box-border-color: var(--color-background-primary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-block-end: $b-2 solid var(--box-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-spacing-svg {
|
.title-wrapper {
|
||||||
@extend .attr-title;
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-attr-title {
|
||||||
|
color: var(--entry-foreground-color-hover);
|
||||||
|
padding-block: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.svg-row {
|
.svg-row {
|
||||||
@extend .attr-row;
|
@extend .attr-row;
|
||||||
|
block-size: $sz-36;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-children {
|
.button-children {
|
||||||
@ -23,12 +35,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.attributes-subtitle {
|
.attributes-subtitle {
|
||||||
@include deprecated.uppercaseTitleTipography;
|
@include use-typography("headline-small");
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
height: deprecated.$s-32;
|
block-size: $sz-32;
|
||||||
span {
|
span {
|
||||||
height: deprecated.$s-32;
|
block-size: $sz-32;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -157,7 +157,8 @@
|
|||||||
[:div {:class (stl/css :attributes-block)}
|
[:div {:class (stl/css :attributes-block)}
|
||||||
[:> inspect-title-bar*
|
[:> inspect-title-bar*
|
||||||
{:title (tr "inspect.attributes.typography")
|
{:title (tr "inspect.attributes.typography")
|
||||||
:class (stl/css :title-spacing-text)}]
|
:class (stl/css :title-wrapper)
|
||||||
|
:title-class (stl/css :text-atrr-title)}]
|
||||||
|
|
||||||
(for [shape shapes]
|
(for [shape shapes]
|
||||||
[:& text-block {:shape shape
|
[:& text-block {:shape shape
|
||||||
|
|||||||
@ -5,23 +5,37 @@
|
|||||||
// Copyright (c) KALEIDOS INC
|
// Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
@use "refactor/common-refactor.scss" as deprecated;
|
@use "refactor/common-refactor.scss" as deprecated;
|
||||||
|
@use "ds/_borders.scss" as *;
|
||||||
|
@use "ds/_sizes.scss" as *;
|
||||||
|
@use "ds/_utils.scss" as *;
|
||||||
|
@use "ds/typography.scss" as *;
|
||||||
|
|
||||||
.attributes-block {
|
.attributes-block {
|
||||||
@include deprecated.flexColumn;
|
--box-border-color: var(--color-background-primary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-block-end: $b-2 solid var(--box-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-spacing-text {
|
.title-wrapper {
|
||||||
@extend .attr-title;
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-attr-title {
|
||||||
|
color: var(--entry-foreground-color-hover);
|
||||||
|
padding-block: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.attributes-content {
|
.attributes-content {
|
||||||
@include deprecated.flexColumn;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--sp-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-row {
|
.text-row {
|
||||||
@extend .attr-row;
|
@extend .attr-row;
|
||||||
height: unset;
|
block-size: unset;
|
||||||
min-height: deprecated.$s-32;
|
min-block-size: $sz-36;
|
||||||
:global(.attr-value) {
|
:global(.attr-value) {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@ -32,20 +46,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.attributes-content-row {
|
.attributes-content-row {
|
||||||
max-width: deprecated.$s-240;
|
max-inline-size: px2rem(240);
|
||||||
min-height: calc(deprecated.$s-2 + deprecated.$s-32);
|
min-block-size: px2rem(34);
|
||||||
border-radius: deprecated.$br-8;
|
border-radius: $br-8;
|
||||||
border: deprecated.$s-1 solid var(--menu-border-color-disabled);
|
border: $b-1 solid var(--menu-border-color-disabled);
|
||||||
margin-top: deprecated.$s-4;
|
margin-block-start: var(--sp-xs);
|
||||||
.content {
|
.content {
|
||||||
@include deprecated.bodySmallTypography;
|
@include use-typography("body-small");
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: deprecated.$s-4 0;
|
padding: var(--sp-xs) 0;
|
||||||
color: var(--color-foreground-secondary);
|
color: var(--color-foreground-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border: deprecated.$s-1 solid var(--color-background-tertiary);
|
border: $b-1 solid var(--color-background-tertiary);
|
||||||
background-color: var(--menu-background-color);
|
background-color: var(--menu-background-color);
|
||||||
.content {
|
.content {
|
||||||
color: var(--menu-foreground-color-hover);
|
color: var(--menu-foreground-color-hover);
|
||||||
|
|||||||
@ -42,7 +42,8 @@
|
|||||||
[:div {:class (stl/css :attributes-block)}
|
[:div {:class (stl/css :attributes-block)}
|
||||||
[:> inspect-title-bar*
|
[:> inspect-title-bar*
|
||||||
{:title (if is-container? (tr "inspect.attributes.variants") (tr "inspect.attributes.variant"))
|
{:title (if is-container? (tr "inspect.attributes.variants") (tr "inspect.attributes.variant"))
|
||||||
:class (stl/css :title-spacing-variant)}]
|
:class (stl/css :title-wrapper)
|
||||||
|
:title-class (stl/css :variant-attr-title)}]
|
||||||
|
|
||||||
(for [[pos property] (map-indexed vector properties)]
|
(for [[pos property] (map-indexed vector properties)]
|
||||||
[:> variant-block* {:key (dm/str "variant-property-" pos) :name (:name property) :value (:value property)}])]))
|
[:> variant-block* {:key (dm/str "variant-property-" pos) :name (:name property) :value (:value property)}])]))
|
||||||
|
|||||||
@ -5,18 +5,29 @@
|
|||||||
// Copyright (c) KALEIDOS INC
|
// Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
@use "refactor/common-refactor.scss" as deprecated;
|
@use "refactor/common-refactor.scss" as deprecated;
|
||||||
|
@use "ds/_borders.scss" as *;
|
||||||
|
@use "ds/_sizes.scss" as *;
|
||||||
|
|
||||||
.attributes-block {
|
.attributes-block {
|
||||||
@include deprecated.flexColumn;
|
--box-border-color: var(--color-background-primary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-block-end: $b-2 solid var(--box-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-spacing-variant {
|
.title-wrapper {
|
||||||
@extend .attr-title;
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variant-attr-title {
|
||||||
|
color: var(--entry-foreground-color-hover);
|
||||||
|
padding-block: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.variant-row {
|
.variant-row {
|
||||||
@extend .attr-row;
|
@extend .attr-row;
|
||||||
height: fit-content;
|
block-size: fit-content;
|
||||||
|
min-block-size: $sz-36;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-children {
|
.button-children {
|
||||||
|
|||||||
@ -51,7 +51,8 @@
|
|||||||
[:div {:class (stl/css :attributes-block)}
|
[:div {:class (stl/css :attributes-block)}
|
||||||
[:> inspect-title-bar*
|
[:> inspect-title-bar*
|
||||||
{:title "Visibility"
|
{:title "Visibility"
|
||||||
:class (stl/css :title-spacing-visibility)}
|
:class (stl/css :title-wrapper)
|
||||||
|
:title-class (stl/css :visibility-attr-title)}
|
||||||
|
|
||||||
(when (= (count shapes) 1)
|
(when (= (count shapes) 1)
|
||||||
[:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties)
|
[:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties)
|
||||||
|
|||||||
@ -5,17 +5,28 @@
|
|||||||
// Copyright (c) KALEIDOS INC
|
// Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
@use "refactor/common-refactor.scss" as deprecated;
|
@use "refactor/common-refactor.scss" as deprecated;
|
||||||
|
@use "ds/_borders.scss" as *;
|
||||||
|
@use "ds/_sizes.scss" as *;
|
||||||
|
|
||||||
.attributes-block {
|
.attributes-block {
|
||||||
@include deprecated.flexColumn;
|
--box-border-color: var(--color-background-primary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-block-end: $b-2 solid var(--box-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-spacing-visibility {
|
.title-wrapper {
|
||||||
@extend .attr-title;
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visibility-attr-title {
|
||||||
|
color: var(--entry-foreground-color-hover);
|
||||||
|
padding-block: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.visibility-row {
|
.visibility-row {
|
||||||
@extend .attr-row;
|
@extend .attr-row;
|
||||||
|
block-size: $sz-36;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-children {
|
.button-children {
|
||||||
@ -23,5 +34,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.copy-btn-title {
|
.copy-btn-title {
|
||||||
max-width: deprecated.$s-28;
|
max-inline-size: $sz-28;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -186,6 +186,7 @@
|
|||||||
[:> styles-tab* {:color-space color-space
|
[:> styles-tab* {:color-space color-space
|
||||||
:objects objects
|
:objects objects
|
||||||
:shapes shapes
|
:shapes shapes
|
||||||
|
:from from
|
||||||
:libraries libraries
|
:libraries libraries
|
||||||
:file-id file-id}]
|
:file-id file-id}]
|
||||||
:computed
|
:computed
|
||||||
|
|||||||
@ -24,10 +24,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewer-code {
|
|
||||||
padding-inline-start: var(--sp-s);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tool-windows {
|
.tool-windows {
|
||||||
block-size: 100%;
|
block-size: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@ -90,7 +90,7 @@
|
|||||||
:multiple))
|
:multiple))
|
||||||
|
|
||||||
(mf/defc styles-tab*
|
(mf/defc styles-tab*
|
||||||
[{:keys [color-space shapes libraries objects file-id]}]
|
[{:keys [color-space shapes libraries objects file-id from]}]
|
||||||
(let [data (dm/get-in libraries [file-id :data])
|
(let [data (dm/get-in libraries [file-id :data])
|
||||||
first-shape (first shapes)
|
first-shape (first shapes)
|
||||||
first-component (ctkl/get-component data (:component-id first-shape))
|
first-component (ctkl/get-component data (:component-id first-shape))
|
||||||
@ -131,7 +131,8 @@
|
|||||||
(mf/deps shorthands*)
|
(mf/deps shorthands*)
|
||||||
(fn [shorthand]
|
(fn [shorthand]
|
||||||
(swap! shorthands* assoc (:panel shorthand) (:property shorthand))))]
|
(swap! shorthands* assoc (:panel shorthand) (:property shorthand))))]
|
||||||
[:ol {:class (stl/css :styles-tab) :aria-label (tr "labels.styles")}
|
[:ol {:class (stl/css-case :styles-tab true
|
||||||
|
:styles-tab-workspace (= from :workspace)) :aria-label (tr "labels.styles")}
|
||||||
;; TOKENS PANEL
|
;; TOKENS PANEL
|
||||||
(when (or (seq active-themes) (seq active-sets))
|
(when (or (seq active-themes) (seq active-sets))
|
||||||
[:li
|
[:li
|
||||||
|
|||||||
@ -7,5 +7,9 @@
|
|||||||
@use "ds/_utils.scss" as *;
|
@use "ds/_utils.scss" as *;
|
||||||
|
|
||||||
.styles-tab {
|
.styles-tab {
|
||||||
block-size: calc(100vh - px2rem(200)); // TODO: Fix this hardcoded value
|
block-size: calc(100vh - px2rem(140)); // TODO: Fix this hardcoded value
|
||||||
|
}
|
||||||
|
|
||||||
|
.styles-tab-workspace {
|
||||||
|
block-size: calc(100vh - px2rem(180)); // TODO: Fix this hardcoded value
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,6 +60,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.property-detail-text {
|
.property-detail-text {
|
||||||
|
@include use-typography("body-small");
|
||||||
color: var(--detail-color);
|
color: var(--detail-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
(case type
|
(case type
|
||||||
:variant (tr "inspect.tabs.styles.variants-panel")
|
:variant (tr "inspect.tabs.styles.variants-panel")
|
||||||
:token (tr "inspect.tabs.styles.token-panel")
|
:token (tr "inspect.tabs.styles.token-panel")
|
||||||
:geometry (tr "inspect.tabs.styles.geometry-panel")
|
:geometry (tr "inspect.attributes.size")
|
||||||
:fill (tr "labels.fill")
|
:fill (tr "labels.fill")
|
||||||
:stroke (tr "labels.stroke")
|
:stroke (tr "labels.stroke")
|
||||||
:text (tr "labels.text")
|
:text (tr "labels.text")
|
||||||
|
|||||||
@ -33,7 +33,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--title-gap);
|
gap: var(--title-gap);
|
||||||
padding-block: var(--title-padding);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.disclosure-button {
|
.disclosure-button {
|
||||||
@ -52,4 +51,5 @@
|
|||||||
@include use-typography("headline-small");
|
@include use-typography("headline-small");
|
||||||
flex: 1;
|
flex: 1;
|
||||||
color: var(--title-color);
|
color: var(--title-color);
|
||||||
|
padding-block: var(--title-padding);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -148,11 +148,12 @@
|
|||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps index prefix is-move)
|
(mf/deps index prefix is-move)
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(dom/stop-propagation event)
|
(when (dom/left-mouse? event)
|
||||||
(dom/prevent-default event)
|
(dom/stop-propagation event)
|
||||||
|
(dom/prevent-default event)
|
||||||
|
|
||||||
(when ^boolean is-move
|
(when ^boolean is-move
|
||||||
(st/emit! (drp/start-move-handler index prefix)))))]
|
(st/emit! (drp/start-move-handler index prefix))))))]
|
||||||
|
|
||||||
[:g.handler {:pointer-events (if ^boolean is-draw "none" "visible")}
|
[:g.handler {:pointer-events (if ^boolean is-draw "none" "visible")}
|
||||||
[:line
|
[:line
|
||||||
|
|||||||
@ -44,39 +44,6 @@
|
|||||||
[(seq (array/sort! empty))
|
[(seq (array/sort! empty))
|
||||||
(seq (array/sort! filled))]))))
|
(seq (array/sort! filled))]))))
|
||||||
|
|
||||||
(mf/defc selected-set-info*
|
|
||||||
{::mf/private true}
|
|
||||||
[{:keys [tokens-lib selected-token-set-id]}]
|
|
||||||
(let [selected-token-set
|
|
||||||
(mf/with-memo [tokens-lib]
|
|
||||||
(when selected-token-set-id
|
|
||||||
(some-> tokens-lib (ctob/get-set selected-token-set-id))))
|
|
||||||
|
|
||||||
active-token-sets-names
|
|
||||||
(mf/with-memo [tokens-lib]
|
|
||||||
(some-> tokens-lib (ctob/get-active-themes-set-names)))
|
|
||||||
|
|
||||||
token-set-active?
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps active-token-sets-names)
|
|
||||||
(fn [name]
|
|
||||||
(contains? active-token-sets-names name)))]
|
|
||||||
[:div {:class (stl/css :sets-header-container)}
|
|
||||||
[:> text* {:as "span"
|
|
||||||
:typography "headline-small"
|
|
||||||
:class (stl/css :sets-header)}
|
|
||||||
(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")}
|
|
||||||
;; 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")]])]]))
|
|
||||||
|
|
||||||
(mf/defc tokens-section*
|
(mf/defc tokens-section*
|
||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
[{:keys [tokens-lib active-tokens resolved-active-tokens]}]
|
[{:keys [tokens-lib active-tokens resolved-active-tokens]}]
|
||||||
@ -98,7 +65,9 @@
|
|||||||
selected-token-set-id
|
selected-token-set-id
|
||||||
(mf/deref refs/selected-token-set-id)
|
(mf/deref refs/selected-token-set-id)
|
||||||
|
|
||||||
|
selected-token-set
|
||||||
|
(when selected-token-set-id
|
||||||
|
(some-> tokens-lib (ctob/get-set selected-token-set-id)))
|
||||||
|
|
||||||
;; If we have not selected any set explicitly we just
|
;; If we have not selected any set explicitly we just
|
||||||
;; select the first one from the list of sets
|
;; select the first one from the list of sets
|
||||||
@ -123,9 +92,15 @@
|
|||||||
tokens)]
|
tokens)]
|
||||||
(ctob/group-by-type tokens)))
|
(ctob/group-by-type tokens)))
|
||||||
|
|
||||||
|
active-token-sets-names
|
||||||
|
(mf/with-memo [tokens-lib]
|
||||||
|
(some-> tokens-lib (ctob/get-active-themes-set-names)))
|
||||||
|
|
||||||
|
token-set-active?
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps active-token-sets-names)
|
||||||
|
(fn [name]
|
||||||
|
(contains? active-token-sets-names name)))
|
||||||
|
|
||||||
[empty-group filled-group]
|
[empty-group filled-group]
|
||||||
(mf/with-memo [tokens-by-type]
|
(mf/with-memo [tokens-by-type]
|
||||||
@ -143,27 +118,34 @@
|
|||||||
|
|
||||||
[:*
|
[:*
|
||||||
[:& token-context-menu]
|
[:& token-context-menu]
|
||||||
|
[:div {:class (stl/css :sets-header-container)}
|
||||||
[:& selected-set-info* {:tokens-lib tokens-lib
|
[:> text* {:as "span" :typography "headline-small" :class (stl/css :sets-header)} (tr "workspace.tokens.tokens-section-title" (ctob/get-name selected-token-set))]
|
||||||
:selected-token-set-id selected-token-set-id}]
|
[: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")]])]]
|
||||||
|
|
||||||
(for [type filled-group]
|
(for [type filled-group]
|
||||||
(let [tokens (get tokens-by-type type)]
|
(let [tokens (get tokens-by-type type)]
|
||||||
[:> token-group* {:key (name type)
|
[:> token-group* {:key (name type)
|
||||||
:tokens tokens
|
:is-open (get open-status type false)
|
||||||
:is-expanded (get open-status type false)
|
|
||||||
:type type
|
:type type
|
||||||
:selected-ids selected
|
:selected-ids selected
|
||||||
:selected-shapes selected-shapes
|
:selected-shapes selected-shapes
|
||||||
:is-selected-inside-layout is-selected-inside-layout
|
:is-selected-inside-layout is-selected-inside-layout
|
||||||
:active-theme-tokens resolved-active-tokens
|
:active-theme-tokens resolved-active-tokens
|
||||||
:tokens-lib tokens-lib
|
:tokens tokens}]))
|
||||||
:selected-token-set-id selected-token-set-id}]))
|
|
||||||
|
|
||||||
(for [type empty-group]
|
(for [type empty-group]
|
||||||
[:> token-group* {:key (name type)
|
[:> token-group* {:key (name type)
|
||||||
:tokens []
|
|
||||||
:type type
|
:type type
|
||||||
:selected-shapes selected-shapes
|
:selected-shapes selected-shapes
|
||||||
:is-selected-inside-layout is-selected-inside-layout
|
:is-selected-inside-layout :is-selected-inside-layout
|
||||||
:active-theme-tokens resolved-active-tokens}])]))
|
:active-theme-tokens resolved-active-tokens
|
||||||
|
:tokens []}])]))
|
||||||
|
|||||||
@ -8,9 +8,6 @@
|
|||||||
(ns app.main.ui.workspace.tokens.management.group
|
(ns app.main.ui.workspace.tokens.management.group
|
||||||
(:require-macros [app.main.style :as stl])
|
(:require-macros [app.main.style :as stl])
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
|
||||||
[app.common.data.macros :as dm]
|
|
||||||
[app.common.types.tokens-lib :as ctob]
|
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
[app.main.data.workspace.tokens.application :as dwta]
|
[app.main.data.workspace.tokens.application :as dwta]
|
||||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||||
@ -19,70 +16,51 @@
|
|||||||
[app.main.ui.context :as ctx]
|
[app.main.ui.context :as ctx]
|
||||||
[app.main.ui.ds.buttons.icon-button :refer [icon-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.assets.icon :as i]
|
||||||
[app.main.ui.ds.layers.layer-button :refer [layer-button*]]
|
[app.main.ui.workspace.sidebar.assets.common :as cmm]
|
||||||
[app.main.ui.workspace.tokens.management.token-tree :refer [token-tree*]]
|
[app.main.ui.workspace.tokens.management.token-pill :refer [token-pill*]]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.i18n :refer [tr]]
|
[app.util.i18n :refer [tr]]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
|
||||||
(defn token-section-icon
|
(defn token-section-icon
|
||||||
[type]
|
[type]
|
||||||
(case type
|
(case type
|
||||||
:border-radius i/corner-radius
|
:border-radius "corner-radius"
|
||||||
:color i/drop
|
:color "drop"
|
||||||
:boolean i/boolean-difference
|
:boolean "boolean-difference"
|
||||||
:font-family i/text-font-family
|
:font-family "text-font-family"
|
||||||
:font-size i/text-font-size
|
:font-size "text-font-size"
|
||||||
:letter-spacing i/text-letterspacing
|
:letter-spacing "text-letterspacing"
|
||||||
:text-case i/text-mixed
|
:text-case "text-mixed"
|
||||||
:text-decoration i/text-underlined
|
:text-decoration "text-underlined"
|
||||||
:font-weight i/text-font-weight
|
:font-weight "text-font-weight"
|
||||||
:typography i/text-typography
|
:typography "text-typography"
|
||||||
:opacity i/percentage
|
:opacity "percentage"
|
||||||
:number i/number
|
:number "number"
|
||||||
:rotation i/rotation
|
:rotation "rotation"
|
||||||
:spacing i/padding-extended
|
:spacing "padding-extended"
|
||||||
:string i/text-mixed
|
:string "text-mixed"
|
||||||
:stroke-width i/stroke-size
|
:stroke-width "stroke-size"
|
||||||
:dimensions i/expand
|
:dimensions "expand"
|
||||||
:sizing i/expand
|
:sizing "expand"
|
||||||
:shadow i/drop-shadow
|
:shadow "drop-shadow"
|
||||||
"add"))
|
"add"))
|
||||||
|
|
||||||
(def ^:private schema:token-group
|
|
||||||
[:map
|
|
||||||
[:type :keyword]
|
|
||||||
[:tokens :any]
|
|
||||||
[:selected-shapes :any]
|
|
||||||
[:is-selected-inside-layout {:optional true} [:maybe :boolean]]
|
|
||||||
[:active-theme-tokens {:optional true} :any]
|
|
||||||
[:selected-token-set-id {:optional true} :any]
|
|
||||||
[:tokens-lib {:optional true} :any]
|
|
||||||
[:on-token-pill-click {:optional true} fn?]
|
|
||||||
[:on-context-menu {:optional true} fn?]])
|
|
||||||
|
|
||||||
(mf/defc token-group*
|
(mf/defc token-group*
|
||||||
{::mf/schema schema:token-group}
|
{::mf/private true}
|
||||||
[{:keys [type tokens selected-shapes is-selected-inside-layout active-theme-tokens selected-token-set-id tokens-lib is-expanded selected-ids]}]
|
[{:keys [type tokens selected-shapes is-selected-inside-layout active-theme-tokens is-open selected-ids]}]
|
||||||
(let [{:keys [modal title]}
|
(let [{:keys [modal title]}
|
||||||
(get dwta/token-properties type)
|
(get dwta/token-properties type)
|
||||||
editing-ref (mf/deref refs/workspace-editor-state)
|
editing-ref (mf/deref refs/workspace-editor-state)
|
||||||
not-editing? (empty? editing-ref)
|
not-editing? (empty? editing-ref)
|
||||||
|
|
||||||
is-expanded (d/nilv is-expanded false)
|
|
||||||
|
|
||||||
can-edit?
|
can-edit?
|
||||||
(mf/use-ctx ctx/can-edit?)
|
(mf/use-ctx ctx/can-edit?)
|
||||||
|
|
||||||
is-selected-inside-layout (d/nilv is-selected-inside-layout false)
|
|
||||||
|
|
||||||
tokens
|
tokens
|
||||||
(mf/with-memo [tokens]
|
(mf/with-memo [tokens]
|
||||||
(vec (sort-by :name tokens)))
|
(vec (sort-by :name tokens)))
|
||||||
|
|
||||||
expandable? (d/nilv (seq tokens) false)
|
|
||||||
|
|
||||||
on-context-menu
|
on-context-menu
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(fn [event token]
|
(fn [event token]
|
||||||
@ -95,8 +73,8 @@
|
|||||||
|
|
||||||
on-toggle-open-click
|
on-toggle-open-click
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps is-expanded type)
|
(mf/deps is-open type)
|
||||||
#(st/emit! (dwtl/set-token-type-section-open type (not is-expanded))))
|
#(st/emit! (dwtl/set-token-type-section-open type (not is-open))))
|
||||||
|
|
||||||
on-popover-open-click
|
on-popover-open-click
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
@ -118,36 +96,33 @@
|
|||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps not-editing? selected-ids)
|
(mf/deps not-editing? selected-ids)
|
||||||
(fn [event token]
|
(fn [event token]
|
||||||
(let [token (ctob/get-token tokens-lib selected-token-set-id (:id token))]
|
(dom/stop-propagation event)
|
||||||
(dom/stop-propagation event)
|
(when (and not-editing? (seq selected-shapes) (not= (:type token) :number))
|
||||||
(when (and not-editing? (seq selected-shapes) (not= (:type token) :number))
|
(st/emit! (dwta/toggle-token {:token token
|
||||||
(st/emit! (dwta/toggle-token {:token token
|
:shape-ids selected-ids})))))]
|
||||||
:shape-ids selected-ids}))))))]
|
|
||||||
|
|
||||||
[:div {:class (stl/css :token-section-wrapper)
|
[:div {:on-click on-toggle-open-click :class (stl/css :token-section-wrapper)}
|
||||||
:data-testid (dm/str "section-" (name type))}
|
[:> cmm/asset-section* {:icon (token-section-icon type)
|
||||||
[:> layer-button* {:label title
|
:title title
|
||||||
:expanded is-expanded
|
:section :tokens
|
||||||
:description (when expandable? (dm/str (count tokens)))
|
:assets-count (count tokens)
|
||||||
:is-expandable expandable?
|
:is-open is-open}
|
||||||
:aria-expanded is-expanded
|
[:> cmm/asset-section-block* {:role :title-button}
|
||||||
:aria-controls (dm/str "token-tree-" (name type))
|
(when can-edit?
|
||||||
:on-toggle-expand on-toggle-open-click
|
[:> icon-button* {:on-click on-popover-open-click
|
||||||
:icon (token-section-icon type)}
|
:variant "ghost"
|
||||||
(when can-edit?
|
:icon i/add
|
||||||
[:> icon-button* {:id (str "add-token-button-" title)
|
:id (str "add-token-button-" title)
|
||||||
:icon "add"
|
:aria-label (tr "workspace.tokens.add-token" title)}])]
|
||||||
:aria-label (tr "workspace.tokens.add-token" title)
|
(when is-open
|
||||||
:variant "ghost"
|
[:> cmm/asset-section-block* {:role :content}
|
||||||
:on-click on-popover-open-click
|
[:div {:class (stl/css :token-pills-wrapper)}
|
||||||
:class (stl/css :token-section-icon)}])]
|
(for [token tokens]
|
||||||
(when is-expanded
|
[:> token-pill*
|
||||||
[:> token-tree* {:tokens tokens
|
{:key (:name token)
|
||||||
:id (dm/str "token-tree-" (name type))
|
:token token
|
||||||
:tokens-lib tokens-lib
|
:selected-shapes selected-shapes
|
||||||
:selected-shapes selected-shapes
|
:is-selected-inside-layout is-selected-inside-layout
|
||||||
:active-theme-tokens active-theme-tokens
|
:active-theme-tokens active-theme-tokens
|
||||||
:selected-token-set-id selected-token-set-id
|
:on-click on-token-pill-click
|
||||||
:is-selected-inside-layout is-selected-inside-layout
|
:on-context-menu on-context-menu}])]])]]))
|
||||||
:on-token-pill-click on-token-pill-click
|
|
||||||
:on-context-menu on-context-menu}])]))
|
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
.token-pills-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--sp-xs);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
@ -307,9 +307,10 @@
|
|||||||
:class (stl/css :token-pill-icon)}])
|
:class (stl/css :token-pill-icon)}])
|
||||||
|
|
||||||
(if contains-path?
|
(if contains-path?
|
||||||
(let [[_ last-part] (cpn/split-by-last-period name)]
|
(let [[first-part last-part] (cpn/split-by-last-period name)]
|
||||||
[:span {:class (stl/css :divided-name-wrapper)
|
[:span {:class (stl/css :divided-name-wrapper)
|
||||||
:aria-label name}
|
:aria-label name}
|
||||||
|
[:span {:class (stl/css :first-name-wrapper)} first-part]
|
||||||
[:span {:class (stl/css :last-name-wrapper)} last-part]])
|
[:span {:class (stl/css :last-name-wrapper)} last-part]])
|
||||||
[:span {:class (stl/css :name-wrapper)
|
[:span {:class (stl/css :name-wrapper)
|
||||||
:aria-label name}
|
:aria-label name}
|
||||||
|
|||||||
@ -1,110 +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.main.ui.workspace.tokens.management.token-tree
|
|
||||||
(:require-macros [app.main.style :as stl])
|
|
||||||
(:require
|
|
||||||
[app.common.path-names :as cpn]
|
|
||||||
[app.common.types.tokens-lib :as ctob]
|
|
||||||
[app.main.ui.ds.layers.layer-button :refer [layer-button*]]
|
|
||||||
[app.main.ui.workspace.tokens.management.token-pill :refer [token-pill*]]
|
|
||||||
[rumext.v2 :as mf]))
|
|
||||||
|
|
||||||
(def ^:private schema:folder-node
|
|
||||||
[:map
|
|
||||||
[:node :any]
|
|
||||||
[:selected-shapes :any]
|
|
||||||
[:is-selected-inside-layout {:optional true} :boolean]
|
|
||||||
[:active-theme-tokens {:optional true} :any]
|
|
||||||
[:selected-token-set-id {:optional true} :any]
|
|
||||||
[:tokens-lib {:optional true} :any]
|
|
||||||
[:on-token-pill-click {:optional true} fn?]
|
|
||||||
[:on-context-menu {:optional true} fn?]])
|
|
||||||
|
|
||||||
(mf/defc folder-node*
|
|
||||||
{::mf/schema schema:folder-node}
|
|
||||||
[{:keys [node selected-shapes is-selected-inside-layout active-theme-tokens selected-token-set-id tokens-lib on-token-pill-click on-context-menu]}]
|
|
||||||
(let [expanded* (mf/use-state false)
|
|
||||||
expanded (deref expanded*)
|
|
||||||
swap-folder-expanded #(swap! expanded* not)]
|
|
||||||
[:li {:class (stl/css :folder-node)}
|
|
||||||
[:> layer-button* {:label (:name node)
|
|
||||||
:expanded expanded
|
|
||||||
:aria-expanded expanded
|
|
||||||
:aria-controls (str "folder-children-" (:path node))
|
|
||||||
:is-expandable (not (:leaf node))
|
|
||||||
:on-toggle-expand swap-folder-expanded}]
|
|
||||||
(when expanded
|
|
||||||
(let [children-fn (:children-fn node)]
|
|
||||||
[:div {:class (stl/css :folder-children-wrapper)
|
|
||||||
:id (str "folder-children-" (:path node))}
|
|
||||||
(when children-fn
|
|
||||||
(let [children (children-fn)]
|
|
||||||
(for [child children]
|
|
||||||
(if (not (:leaf child))
|
|
||||||
[:ul {:class (stl/css :node-parent)}
|
|
||||||
[:> folder-node* {:key (:path child)
|
|
||||||
:node child
|
|
||||||
:selected-shapes selected-shapes
|
|
||||||
:is-selected-inside-layout is-selected-inside-layout
|
|
||||||
:active-theme-tokens active-theme-tokens
|
|
||||||
:on-token-pill-click on-token-pill-click
|
|
||||||
:on-context-menu on-context-menu
|
|
||||||
:tokens-lib tokens-lib
|
|
||||||
:selected-token-set-id selected-token-set-id}]]
|
|
||||||
(let [id (:id (:leaf child))
|
|
||||||
token (ctob/get-token tokens-lib selected-token-set-id id)]
|
|
||||||
[:> token-pill*
|
|
||||||
{:key id
|
|
||||||
:token token
|
|
||||||
:selected-shapes selected-shapes
|
|
||||||
:is-selected-inside-layout is-selected-inside-layout
|
|
||||||
:active-theme-tokens active-theme-tokens
|
|
||||||
:on-click on-token-pill-click
|
|
||||||
:on-context-menu on-context-menu}])))))]))]))
|
|
||||||
|
|
||||||
(def ^:private schema:token-tree
|
|
||||||
[:map
|
|
||||||
[:tokens :any]
|
|
||||||
[:selected-shapes :any]
|
|
||||||
[:is-selected-inside-layout {:optional true} :boolean]
|
|
||||||
[:active-theme-tokens {:optional true} :any]
|
|
||||||
[:selected-token-set-id {:optional true} :any]
|
|
||||||
[:tokens-lib {:optional true} :any]
|
|
||||||
[:on-token-pill-click {:optional true} fn?]
|
|
||||||
[:on-context-menu {:optional true} fn?]])
|
|
||||||
|
|
||||||
(mf/defc token-tree*
|
|
||||||
{::mf/schema schema:token-tree}
|
|
||||||
[{:keys [tokens selected-shapes is-selected-inside-layout active-theme-tokens tokens-lib selected-token-set-id on-token-pill-click on-context-menu]}]
|
|
||||||
(let [separator "."
|
|
||||||
tree (mf/use-memo
|
|
||||||
(mf/deps tokens)
|
|
||||||
(fn []
|
|
||||||
(cpn/build-tree-root tokens separator)))]
|
|
||||||
[:div {:class (stl/css :token-tree-wrapper)}
|
|
||||||
(for [node tree]
|
|
||||||
[:ul {:class (stl/css :node-parent)
|
|
||||||
:key (:path node)
|
|
||||||
:style {:--node-depth (inc (:depth node))}}
|
|
||||||
(if (:leaf node)
|
|
||||||
(let [token (ctob/get-token tokens-lib selected-token-set-id (get-in node [:leaf :id]))]
|
|
||||||
[:> token-pill*
|
|
||||||
{:token token
|
|
||||||
:selected-shapes selected-shapes
|
|
||||||
:is-selected-inside-layout is-selected-inside-layout
|
|
||||||
:active-theme-tokens active-theme-tokens
|
|
||||||
:on-click on-token-pill-click
|
|
||||||
:on-context-menu on-context-menu}])
|
|
||||||
;; Render segment folder
|
|
||||||
[:> folder-node* {:node node
|
|
||||||
:selected-shapes selected-shapes
|
|
||||||
:is-selected-inside-layout is-selected-inside-layout
|
|
||||||
:active-theme-tokens active-theme-tokens
|
|
||||||
:on-token-pill-click on-token-pill-click
|
|
||||||
:on-context-menu on-context-menu
|
|
||||||
:tokens-lib tokens-lib
|
|
||||||
:selected-token-set-id selected-token-set-id}])])]))
|
|
||||||
@ -1,39 +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
|
|
||||||
|
|
||||||
@use "ds/_borders.scss" as *;
|
|
||||||
|
|
||||||
.token-tree-wrapper {
|
|
||||||
padding-block-end: var(--sp-s);
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-parent {
|
|
||||||
--node-spacing: var(--sp-l);
|
|
||||||
--node-depth: 0;
|
|
||||||
|
|
||||||
margin-block-end: 0;
|
|
||||||
padding-inline-start: calc(var(--node-spacing) * var(--node-depth));
|
|
||||||
}
|
|
||||||
|
|
||||||
.folder-children-wrapper:has(> button) {
|
|
||||||
margin-inline-start: var(--sp-s);
|
|
||||||
padding-inline-start: var(--sp-s);
|
|
||||||
border-inline-start: $b-2 solid var(--color-background-quaternary);
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
column-gap: var(--sp-xs);
|
|
||||||
|
|
||||||
& .node-parent {
|
|
||||||
flex: 1 0 100%;
|
|
||||||
|
|
||||||
&:last-of-type {
|
|
||||||
margin-block-end: var(--sp-s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
& .token-pill {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -37,7 +37,7 @@ This command is going to search for the file located in `frontend/src/app/main/u
|
|||||||
|
|
||||||
## How it works?
|
## How it works?
|
||||||
|
|
||||||
The text editor divides the content in three elements: `root`, `paragraph` and `inline`. An `inline` in terms of content is a styled element that it is displayed in a line inside a block and an `inline` only can have one child (a Text node). A `paragraph` is a **block** element that can contain multiple `inline`s (**inline** elements).
|
The text editor divides the content in three elements: `root`, `paragraph` and `textSpan`. In terms of content, a `textSpan` is a styled element displayed on a line within a block. A `textSpan` can only have one child (a Text node). A `paragraph` is a **block** element that can contain multiple `textSpan`s (**textSpan** elements).
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<div data-itype="root">
|
<div data-itype="root">
|
||||||
@ -53,10 +53,10 @@ This way we only need to deal with a structure like this, where circular nodes a
|
|||||||
```mermaid
|
```mermaid
|
||||||
flowchart TB
|
flowchart TB
|
||||||
root((root)) --> paragraph((paragraph))
|
root((root)) --> paragraph((paragraph))
|
||||||
paragraph --> inline_1((inline))
|
paragraph --> text_span_1((textSpan))
|
||||||
paragraph --> inline_2((inline))
|
paragraph --> text_span_2((textSpan))
|
||||||
inline_1 --> text_1[Hello, ]
|
text_span_1 --> text_1[Hello, ]
|
||||||
inline_2 --> text_2[World!]
|
text_span_2 --> text_2[World!]
|
||||||
```
|
```
|
||||||
|
|
||||||
This is compatible with the way Penpot stores text content.
|
This is compatible with the way Penpot stores text content.
|
||||||
@ -68,6 +68,26 @@ flowchart TB
|
|||||||
paragraph --> text((text))
|
paragraph --> text((text))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## How the TextEditor works?
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
TextEditor -->|handles `selectionchange` events| SelectionController
|
||||||
|
TextEditor -->|handles how the editor dispatches changes| ChangeController
|
||||||
|
```
|
||||||
|
|
||||||
|
The `TextEditor` contains a series of references to DOM elements, one of them is a `contenteditable` element that keeps the sub-elements explained before (root, paragraphs and textspans).
|
||||||
|
|
||||||
|
`SelectionController` listens to the `document` event called `selectionchange`. This event is triggered everytime the focus/selection of the browser changes.
|
||||||
|
|
||||||
|
`ChangeController` is called by the `TextEditor` instance everytime a change is performed on the content of the `contenteditable` element.
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
- `change`: This event is dispatched every time a change is made in the editor. All changes are debounced to prevent dispatching too many change events. This event is also dispatched when there are pending change events and the user blurs the textarea element.
|
||||||
|
|
||||||
|
- `stylechange`: This event is dispatched every time the `currentStyle` changes. This normally happens when the user changes the caret position or the selection and the `currentStyle` is re-computed.
|
||||||
|
|
||||||
## How the code is organized?
|
## How the code is organized?
|
||||||
|
|
||||||
- `editor`: contains everything related to the TextEditor. Where `TextEditor.js` is the main file where all the basic code of the editor is handled. This has been designed so that in the future, when the Web Components API is more stable and has features such as handling selection events within shadow roots we will be able to update this class with little effort.
|
- `editor`: contains everything related to the TextEditor. Where `TextEditor.js` is the main file where all the basic code of the editor is handled. This has been designed so that in the future, when the Web Components API is more stable and has features such as handling selection events within shadow roots we will be able to update this class with little effort.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user