0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-16 08:51:32 -05:00

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

This commit is contained in:
Andrey Antukh 2025-04-01 11:02:32 +02:00
commit 076d64df8f
31 changed files with 339 additions and 141 deletions

View file

@ -55,8 +55,12 @@
- Fix problem with constraints when creating group [Taiga #10455](https://tree.taiga.io/project/penpot/issue/10455)
- Fix opening pen with shortcut multiple times breaks toolbar [Taiga #10566](https://tree.taiga.io/project/penpot/issue/10566)
- Fix actions when workspace is visited first time [Taiga #10548](https://tree.taiga.io/project/penpot/issue/10548)
- Chat icon overlaps "Show" button in carrousel section [Taiga #10542](https://tree.taiga.io/project/penpot/issue/10542)
- Fix assets name on inspect tab [Taiga #10630](https://tree.taiga.io/project/penpot/issue/10630)
- Fix chat icon overlaps "Show" button in carrousel section [Taiga #10542](https://tree.taiga.io/project/penpot/issue/10542)
- Fix incorrect handling of background task result (now task rows are properly marked as completed)
- Fix available size of resize handler [Taiga #10639](https://tree.taiga.io/project/penpot/issue/10639)
- Internal error when install a plugin by penpothub - Try plugin [Taiga #10542](https://tree.taiga.io/project/penpot/issue/10542)
## 2.5.4
@ -73,7 +77,6 @@
methods (add missing team-id prop)
- Fix problem with viewer role and inspect mode [Taiga #9751](https://tree.taiga.io/project/penpot/issue/9751)
- Fix error when clicking on a comment at the viewer's sidebar [Taiga #10465](https://tree.taiga.io/project/penpot/issue/10465)
- Internal error when install a plugin by penpothub - Try plugin [Taiga #10542](https://tree.taiga.io/project/penpot/issue/10542)
## 2.5.3

View file

@ -78,7 +78,10 @@
:always
(update :data select-keys [:id :options :pages :pages-index :components]))
libs (files/get-file-libraries conn file-id)
libs (->> (files/get-file-libraries conn file-id)
(mapv (fn [{:keys [id] :as lib}]
(merge lib (files/get-file cfg id)))))
links (->> (db/query conn :share-link {:file-id file-id})
(mapv (fn [row]
(-> row

View file

@ -1639,7 +1639,21 @@
(if (and (empty? roperations) (empty? applied-tokens))
changes
(let [all-parents (cfh/get-parent-ids (:objects container)
(:id dest-shape))]
(:id dest-shape))
;; Sync tokens of attributes ignored above.
;; FIXME: this probably may be merged with the other calculation
;; of applied tokens, below, and to the calculation only once
;; for all sync-attrs.
applied-tokens (reduce (fn [applied-tokens attr]
(let [attr-group (get ctk/sync-attrs attr)
token-attrs (cto/shape-attr->token-attrs attr)]
(if (not (and (touched attr-group)
omit-touched?))
(into applied-tokens token-attrs)
applied-tokens)))
applied-tokens
ctk/swap-keep-attrs)]
(cond-> changes
(seq roperations)
(-> (update :redo-changes conj (make-change

View file

@ -182,14 +182,27 @@
([shape-attr] (shape-attr->token-attrs shape-attr nil))
([shape-attr changed-sub-attr]
(cond
(= :fills shape-attr) #{:fill}
(and (= :strokes shape-attr) (nil? changed-sub-attr)) #{:stroke-width :stroke-color}
(= :fills shape-attr)
#{:fill}
(and (= :strokes shape-attr) (nil? changed-sub-attr))
#{:stroke-width :stroke-color}
(= :strokes shape-attr)
(cond
(some #{:stroke-color} changed-sub-attr) #{:stroke-color}
(some #{:stroke-width} changed-sub-attr) #{:stroke-width})
(and (= :layout-padding shape-attr) (seq changed-sub-attr)) changed-sub-attr
(and (= :layout-item-margin shape-attr) (seq changed-sub-attr)) changed-sub-attr
(= :layout-padding shape-attr)
(if (seq changed-sub-attr)
changed-sub-attr
#{:p1 :p2 :p3 :p4})
(= :layout-item-margin shape-attr)
(if (seq changed-sub-attr)
changed-sub-attr
#{:m1 :m2 :m3 :m4})
(border-radius-keys shape-attr) #{shape-attr}
(sizing-keys shape-attr) #{shape-attr}
(opacity-keys shape-attr) #{shape-attr}

View file

@ -665,20 +665,20 @@
["$value" :map]
["$type" :string]]]))
(defn has-legacy-format?
(defn get-json-format
"Searches through parsed token file and returns:
- true when first node satisfies `legacy-node?` predicate
- false when first node satisfies `dtcg-node?` predicate
- nil if neither combination is found"
- `:json-format/legacy` when first node satisfies `legacy-node?` predicate
- `:json-format/dtcg` when first node satisfies `dtcg-node?` predicate
- `nil` if neither combination is found"
([data]
(has-legacy-format? data legacy-node? dtcg-node?))
(get-json-format data legacy-node? dtcg-node?))
([data legacy-node? dtcg-node?]
(let [branch? map?
children (fn [node] (vals node))
check-node (fn [node]
(cond
(legacy-node? node) true
(dtcg-node? node) false
(legacy-node? node) :json-format/legacy
(dtcg-node? node) :json-format/dtcg
:else nil))
walk (fn walk [node]
(lazy-seq
@ -690,6 +690,10 @@
(filter some?)
first))))
(defn single-set? [data]
(and (not (contains? data "$metadata"))
(not (contains? data "$themes"))))
;; DEPRECATED
(defn walk-sets-tree-seq
"Walk sets tree as a flat list.
@ -826,6 +830,24 @@
(declare make-tokens-lib)
(defn- legacy-nodes->dtcg-nodes [sets-data]
(walk/postwalk
(fn [node]
(cond-> node
(and (map? node)
(contains? node "value")
(sequential? (get node "value")))
(update "value"
(fn [seq-value]
(map #(set/rename-keys % {"type" "$type"}) seq-value)))
(and (map? node)
(and (contains? node "type")
(contains? node "value")))
(set/rename-keys {"value" "$value"
"type" "$type"})))
sets-data))
(defprotocol ITokensLib
"A library of tokens, sets and themes."
(set-path-exists? [_ path] "if a set at `path` exists")
@ -844,6 +866,8 @@ Will return a value that matches this schema:
(get-active-themes-set-tokens [_] "set of set names that are active in the the active themes")
(encode-dtcg [_] "Encodes library to a dtcg compatible json string")
(decode-dtcg-json [_ parsed-json] "Decodes parsed json containing tokens and converts to library")
(decode-single-set-json [_ set-name tokens] "Decodes parsed json containing single token set and converts to library")
(decode-single-set-legacy-json [_ set-name tokens] "Decodes parsed legacy json containing single token set and converts to library")
(decode-legacy-json [_ parsed-json] "Decodes parsed legacy json containing tokens and converts to library")
(get-all-tokens [_] "all tokens in the lib")
(validate [_]))
@ -1287,6 +1311,17 @@ Will return a value that matches this schema:
(assoc-in ["$metadata" "activeThemes"] active-themes-clear)
(assoc-in ["$metadata" "activeSets"] active-sets))))
(decode-single-set-json [this set-name tokens]
(assert (map? tokens) "expected a map data structure for `data`")
(add-set this (make-token-set :name (normalize-set-name set-name)
:tokens (flatten-nested-tokens-json tokens ""))))
(decode-single-set-legacy-json [this set-name tokens]
(assert (map? tokens) "expected a map data structure for `data`")
(decode-single-set-json this set-name (legacy-nodes->dtcg-nodes tokens)))
(decode-dtcg-json [_ data]
(assert (map? data) "expected a map data structure for `data`")
@ -1370,22 +1405,7 @@ Will return a value that matches this schema:
(decode-legacy-json [this parsed-legacy-json]
(let [other-data (select-keys parsed-legacy-json ["$themes" "$metadata"])
sets-data (dissoc parsed-legacy-json "$themes" "$metadata")
dtcg-sets-data (walk/postwalk
(fn [node]
(cond-> node
(and (map? node)
(contains? node "value")
(sequential? (get node "value")))
(update "value"
(fn [seq-value]
(map #(set/rename-keys % {"type" "$type"}) seq-value)))
(and (map? node)
(and (contains? node "type")
(contains? node "value")))
(set/rename-keys {"value" "$value"
"type" "$type"})))
sets-data)]
dtcg-sets-data (legacy-nodes->dtcg-nodes sets-data)]
(decode-dtcg-json this (merge other-data
dtcg-sets-data))))
(get-all-tokens [this]

View file

@ -0,0 +1,6 @@
{"color":
{"red":
{"100":
{"value":"red",
"type":"color",
"description":""}}}}

View file

@ -0,0 +1,6 @@
{"color":
{"red":
{"100":
{"$value":"red",
"$type":"color",
"$description":""}}}}

View file

@ -1371,6 +1371,30 @@
(t/testing "invalid tokens got discarded"
(t/is (nil? (get-set-token "typography" "H1.Bold")))))))
#?(:clj
(t/deftest single-set-legacy-json-decoding
(let [json (-> (slurp "test/common_tests/types/data/legacy-single-set.json")
(tr/decode-str))
lib (ctob/decode-single-set-legacy-json (ctob/ensure-tokens-lib nil) "single_set" json)
get-set-token (fn [set-name token-name]
(some-> (ctob/get-set lib set-name)
(ctob/get-token token-name)))]
(t/is (= '("single_set") (ctob/get-ordered-set-names lib)))
(t/testing "token added"
(t/is (some? (get-set-token "single_set" "color.red.100")))))))
#?(:clj
(t/deftest single-set-dtcg-json-decoding
(let [json (-> (slurp "test/common_tests/types/data/single-set.json")
(tr/decode-str))
lib (ctob/decode-single-set-json (ctob/ensure-tokens-lib nil) "single_set" json)
get-set-token (fn [set-name token-name]
(some-> (ctob/get-set lib set-name)
(ctob/get-token token-name)))]
(t/is (= '("single_set") (ctob/get-ordered-set-names lib)))
(t/testing "token added"
(t/is (some? (get-set-token "single_set" "color.red.100")))))))
#?(:clj
(t/deftest dtcg-encoding-decoding-json
(let [json (-> (slurp "test/common_tests/types/data/tokens-multi-set-example.json")

View file

@ -8,12 +8,12 @@ source ~/.bashrc
echo "[start-tmux.sh] Installing node dependencies"
pushd ~/penpot/frontend/
corepack up;
corepack install;
yarn install;
yarn run playwright install --with-deps chromium
popd
pushd ~/penpot/exporter/
corepack up;
corepack install;
yarn install
yarn run playwright install --with-deps chromium
popd

View file

@ -52,7 +52,7 @@ services:
## penpot to the internet, or a different host than `localhost`.
# traefik:
# image: traefik:v2.9
# image: traefik:v3.3
# networks:
# - penpot
# command:
@ -88,28 +88,15 @@ services:
- penpot
labels:
- "traefik.enable=true"
# - "traefik.enable=true"
## HTTP: example of labels for the case where penpot will be exposed to the
## internet with only HTTP (without HTTPS) using traefik.
# ## HTTPS: example of labels for the case where penpot will be exposed to the
# ## internet with HTTPS using traefik.
# - "traefik.http.routers.penpot-http.entrypoints=web"
# - "traefik.http.routers.penpot-http.rule=Host(`<DOMAIN_NAME>`)"
# - "traefik.http.services.penpot-http.loadbalancer.server.port=80"
## HTTPS: example of labels for the case where penpot will be exposed to the
## internet with HTTPS using traefik.
# - "traefik.http.middlewares.http-redirect.redirectscheme.scheme=https"
# - "traefik.http.middlewares.http-redirect.redirectscheme.permanent=true"
# - "traefik.http.routers.penpot-http.entrypoints=web"
# - "traefik.http.routers.penpot-http.rule=Host(`<DOMAIN_NAME>`)"
# - "traefik.http.routers.penpot-http.middlewares=http-redirect"
# - "traefik.http.routers.penpot-https.entrypoints=websecure"
# - "traefik.http.routers.penpot-https.rule=Host(`<DOMAIN_NAME>`)"
# - "traefik.http.services.penpot-https.loadbalancer.server.port=80"
# - "traefik.http.routers.penpot-https.tls=true"
# - "traefik.http.routers.penpot-https.entrypoints=websecure"
# - "traefik.http.routers.penpot-https.tls.certresolver=letsencrypt"
# - "traefik.http.routers.penpot-https.tls=true"
environment:
<< : [*penpot-flags, *penpot-http-body-size]
@ -130,8 +117,7 @@ services:
networks:
- penpot
## Configuration envronment variables for the backend
## container.
## Configuration envronment variables for the backend container.
environment:
<< : [*penpot-flags, *penpot-public-uri, *penpot-http-body-size]

View file

@ -200,6 +200,8 @@ server {
}
```
For full documentation, go to the [official website][2]
### Example with CADDY SERVER
```bash
@ -212,4 +214,15 @@ penpot.mycompany.com {
}
```
For full documentation, go to the [official website][3]
### Example with TRAEFIK
In the [Penpot's docker-compose.yaml][4] file, there is a simple example with Traefik.
For full documentation, go to the [official website][5]
[1]: /technical-guide/configuration/
[2]: https://nginx.org/en/docs/index.html
[3]: https://caddyserver.com/docs/
[4]: https://github.com/penpot/penpot/blob/develop/docker/images/docker-compose.yaml
[5]: https://doc.traefik.io/traefik/

View file

@ -245,6 +245,9 @@ export class WorkspacePage extends BaseWebSocketPage {
async clickAssets(clickOptions = {}) {
await this.sidebar.getByText("Assets").click(clickOptions);
}
async clickLayers(clickOptions = {}) {
await this.sidebar.getByText("Layers").click(clickOptions);
}
async openLibrariesModal(clickOptions = {}) {
await this.sidebar.getByTestId("libraries").click(clickOptions);

View file

@ -70,3 +70,55 @@ test("[Taiga #9116] Copy CSS background color in the selected format in the INSP
);
expect(rgbaColorText).toContain("background: rgba(");
});
test("[Taiga #10630] [INSPECT] Style assets not being displayed on info tab", async ({
page,
context,
}) => {
const workspacePage = new WorkspacePage(page);
await workspacePage.setupEmptyFile(page);
await workspacePage.goToWorkspace();
await workspacePage.mockRPC(
"link-file-to-library",
"workspace/link-file-to-library.json",
);
await workspacePage.mockRPC(
"unlink-file-from-library",
"workspace/unlink-file-from-library.json",
);
await workspacePage.mockRPC(
"get-team-shared-files?team-id=*",
"workspace/get-team-shared-libraries-non-empty.json",
);
await workspacePage.clickColorPalette();
await workspacePage.clickAssets();
await workspacePage.mockRPC(/get\-file\?/, "workspace/get-file-library.json");
await workspacePage.openLibrariesModal();
await workspacePage.clickLibrary("Testing library 1");
await workspacePage.closeLibrariesModal();
await expect(
workspacePage.palette.getByRole("button", { name: "test-color-187cd5" }),
).toBeVisible();
await workspacePage.clickLayers();
await workspacePage.rectShapeButton.click();
await workspacePage.clickWithDragViewportAt(128, 128, 200, 100);
await workspacePage.clickLeafLayer("Rectangle");
await workspacePage.palette
.getByRole("button", { name: "test-color-187cd5" })
.click();
const inspectButton = workspacePage.page.getByRole("tab", {
name: "Inspect",
});
await inspectButton.click();
const colorLibraryName = workspacePage.page.getByTestId("color-library-name");
await expect(colorLibraryName).toHaveText("test-color-187cd5");
});

View file

@ -192,3 +192,5 @@
:height 720}])
(def zoom-half-pixel-precision 8)
(def max-input-length 255)

View file

@ -189,6 +189,8 @@
(assoc :share-links share-links)
(assoc :current-team-id team-id)
(assoc :teams {team-id team})
(assoc :files (-> (d/index-by :id libraries)
(assoc (:id file) file)))
(assoc :viewer {:libraries (d/index-by :id libraries)
:users (d/index-by :id users)
:permissions permissions

View file

@ -10,6 +10,7 @@
[app.main.style :as stl])
(:require
[app.common.data :as d]
[app.main.constants :refer [max-input-length]]
[app.main.ui.ds.controls.shared.options-dropdown :refer [options-dropdown*]]
[app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list] :as i]
[app.util.array :as array]
@ -60,6 +61,7 @@
[:id {:optional true} :string]
[:options [:vector schema:combobox-option]]
[:class {:optional true} :string]
[:max-length {:optional true} :int]
[:placeholder {:optional true} :string]
[:disabled {:optional true} :boolean]
[:default-selected {:optional true} :string]
@ -69,7 +71,7 @@
(mf/defc combobox*
{::mf/props :obj
::mf/schema schema:combobox}
[{:keys [id options class placeholder disabled has-error default-selected on-change] :rest props}]
[{:keys [id options class placeholder disabled has-error default-selected on-change max-length] :rest props}]
(let [open* (mf/use-state false)
open (deref open*)
@ -240,6 +242,7 @@
:aria-activedescendant focused
:class (stl/css :input)
:data-testid "combobox-input"
:maxlength (d/nilv max-length max-input-length)
:disabled disabled
:value selected
:on-change on-input-change

View file

@ -10,6 +10,7 @@
[app.main.style :as stl])
(:require
[app.common.data :as d]
[app.main.constants :refer [max-input-length]]
[app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list]]
[app.util.dom :as dom]
[rumext.v2 :as mf]))
@ -20,13 +21,14 @@
[:icon {:optional true}
[:and :string [:fn #(contains? icon-list %)]]]
[:type {:optional true} :string]
[:max-length {:optional true} :int]
[:variant {:optional true} :string]])
(mf/defc input*
{::mf/props :obj
::mf/forward-ref true
::mf/schema schema:input}
[{:keys [icon class type variant] :rest props} ref]
[{:keys [icon class type max-length variant] :rest props} ref]
(let [ref (or ref (mf/use-ref))
type (d/nilv type "text")
props (mf/spread-props props
@ -34,6 +36,7 @@
:input true
:input-with-icon (some? icon))
:ref ref
:maxlength (d/nilv max-length (str max-input-length))
:type type})
on-icon-click

View file

@ -32,16 +32,11 @@
(get-in state [libraries-place file-id :data :colors]))]
(l/derived get-library st/state)))
(defn- get-colors-library [color]
(let [colors-library-v (-> (mf/use-memo
(mf/deps (:file-id color))
#(make-colors-library-ref :viewer-libraries (:file-id color)))
mf/deref)
colors-library-ws (-> (mf/use-memo
(mf/deps (:file-id color))
#(make-colors-library-ref :libraries (:file-id color)))
mf/deref)]
(or colors-library-v colors-library-ws)))
(defn- use-colors-library [color]
(-> (mf/use-memo
(mf/deps (:file-id color))
#(make-colors-library-ref :files (:file-id color)))
mf/deref))
(defn- get-file-colors []
(or (mf/deref file-colors-ref) (mf/deref refs/workspace-file-colors)))
@ -54,7 +49,7 @@
(str/capital $)))
(mf/defc color-row [{:keys [color format copy-data on-change-format]}]
(let [colors-library (get-colors-library color)
(let [colors-library (use-colors-library color)
file-colors (get-file-colors)
color-library-name (get-in (or colors-library file-colors) [(:id color) :name])
color (assoc color :color-library-name color-library-name)
@ -85,7 +80,8 @@
(when color-library-name
[:div {:class (stl/css :second-row)}
[:div {:class (stl/css :color-name-library)}
[:div {:class (stl/css :color-name-library)
:data-testid "color-library-name"}
color-library-name]])]]
[:div {:class (stl/css :image-download)}
@ -146,6 +142,7 @@
(when color-library-name
[:div {:class (stl/css :second-row)}
[:div {:class (stl/css :color-name-library)}
[:div {:class (stl/css :color-name-library)
:data-testid "color-library-name"}
color-library-name]])]])))

View file

@ -70,36 +70,23 @@
visibility: visible;
}
}
.one-line {
max-height: $s-32;
}
.two-line {
display: grid;
grid-template-rows: 1fr 1fr;
grid-template-rows: auto 1fr;
gap: $s-4;
}
.color-name-wrapper {
@include bodySmallTypography;
@include flexColumn;
padding: $s-8 $s-4 $s-8 $s-8;
height: $s-32;
max-width: $s-80;
&.gradient-color {
color: var(--menu-foreground-color);
max-width: $s-124;
}
.color-name-library {
@include bodySmallTypography;
@include textEllipsis;
text-align: left;
height: $s-16;
color: var(--menu-foreground-color-rest);
}
.color-value-wrapper {
@include bodySmallTypography;
height: $s-16;
color: var(--menu-foreground-color);
}
}
.opacity-info {
@ -152,6 +139,7 @@
.color-name-library {
@include inspectValue;
color: var(--menu-foreground-color-rest);
word-break: break-word;
}
.image-download {

View file

@ -110,7 +110,9 @@
[:div {:class (stl/css :resize-area-horiz)
:on-pointer-down on-pointer-down-pages
:on-lost-pointer-capture on-lost-pointer-capture-pages
:on-pointer-move on-pointer-move-pages}])
:on-pointer-move on-pointer-move-pages}
[:div {:class (stl/css :resize-handle-horiz)}]])
[:& layers-toolbox {:size-parent size
:size size-pages}]])

View file

@ -88,6 +88,12 @@ $width-settings-bar-max: $s-500;
position: absolute;
left: 0;
width: 100%;
padding: $s-3 0 $s-1 0;
height: $s-6;
cursor: ns-resize;
}
.resize-handle-horiz {
border-bottom: $s-2 solid var(--resize-area-border-color);
cursor: ns-resize;
}

View file

@ -7,7 +7,9 @@
(ns app.main.ui.workspace.tokens.components.controls.input-tokens
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.constants :refer [max-input-length]]
[app.main.ui.ds.controls.input :refer [input*]]
[rumext.v2 :as mf]))
@ -18,6 +20,7 @@
[:placeholder {:optional true} :string]
[:default-value {:optional true} [:maybe :string]]
[:class {:optional true} :string]
[:max-length {:optional true} :int]
[:error {:optional true} :boolean]
[:value {:optional true} :string]])
@ -25,12 +28,13 @@
{::mf/props :obj
::mf/forward-ref true
::mf/schema schema::input-tokens}
[{:keys [class label id error value children] :rest props} ref]
[{:keys [class label id max-length error value children] :rest props} ref]
(let [ref (or ref (mf/use-ref))
props (mf/spread-props props {:id id
:type "text"
:class (stl/css :input)
:aria-invalid error
:max-length (d/nilv max-length max-input-length)
:value value
:ref ref})]
[:div {:class (dm/str class " " (stl/css-case :wrapper true

View file

@ -106,9 +106,9 @@
[:> heading* {:level 3
:class (stl/css :theme-group-label)
:typography "body-large"}
[:span {:class (stl/css :group-title) :title (tr "workspace.token.group-name")}
[:> icon* {:icon-id "group"}]
group]])
[:div {:class (stl/css :group-title) :title (str (tr "workspace.token.group-name") ": " group)}
[:> icon* {:icon-id "group" :class (stl/css :group-title-icon)}]
[:> text* {:as "span" :typography "body-medium" :class (stl/css :group-title-name)} group]]])
[:ul {:class (stl/css :theme-group-rows-wrapper)}
(for [[_ {:keys [group name] :as theme}] themes
:let [theme-id (ctob/theme-path theme)
@ -126,7 +126,7 @@
:theme-path [(:id theme) (:group theme) (:name theme)]})))]]
[:li {:key theme-id
:class (stl/css :theme-row)}
[:div {:class (stl/css :theme-row-left)}
[:div {:class (stl/css :theme-switch-row)}
;; FIXME: FIREEEEEEEEEE THIS
[:div {:on-click (fn [e]
@ -135,11 +135,12 @@
(st/emit! (wdt/toggle-token-theme-active? group name)))}
[:& switch {:name (tr "workspace.token.theme-name" name)
:on-change (constantly nil)
:selected? selected?}]]
[:> text* {:as "span" :typography "body-medium" :class (stl/css :theme-name)} name]]
:selected? selected?}]]]
[:div {:class (stl/css :theme-name-row)}
[:> text* {:as "span" :typography "body-medium" :class (stl/css :theme-name) :title name} name]]
[:div {:class (stl/css :theme-row-right)}
[:div {:class (stl/css :theme-actions-row)}
(let [sets-count (some-> theme :sets seq count)]
[:> button* {:class (stl/css-case :sets-count-button sets-count
:sets-count-empty-button (not sets-count))
@ -205,6 +206,7 @@
[:> input-tokens* {:id "theme-input"
:label (tr "workspace.token.label.theme")
:type "text"
:max-length 256
:placeholder (tr "workspace.token.label.theme-placeholder")
:on-change on-update-name
:value (mf/ref-val theme-name-ref)

View file

@ -32,8 +32,8 @@
}
.themes-modal-wrapper {
display: grid;
grid-template-rows: 0fr minmax(0, 1fr);
display: flex;
flex-direction: column;
gap: $s-16;
max-height: $s-688;
}
@ -120,6 +120,15 @@
gap: $s-4;
}
.group-title-icon {
flex-shrink: 0;
}
.group-title-name {
flex-grow: 1;
@include textEllipsis;
}
.theme-group-rows-wrapper {
display: flex;
flex-direction: column;
@ -136,26 +145,26 @@
}
.theme-row {
display: flex;
align-items: center;
gap: $s-12;
display: flex;
justify-content: space-between;
gap: $s-16;
}
.theme-row-left {
display: flex;
align-items: center;
gap: $s-16;
.theme-name-row {
@include textEllipsis;
flex-grow: 1;
}
.theme-name {
color: var(--color-foreground-primary);
}
.theme-row-right {
display: flex;
.theme-actions-row {
align-items: center;
display: flex;
gap: $s-6;
flex-shrink: 0;
}
.sets-count-button {

View file

@ -94,6 +94,7 @@
:type "text"
:on-blur on-submit
:on-key-down on-key-down
:maxlength "256"
:auto-focus true
:placeholder (tr "workspace.token.set-edit-placeholder")
:default-value default-value}]))
@ -226,6 +227,7 @@
:on-submit on-edit-submit'}]
[:*
[:div {:class (stl/css :set-name)
:title label
:on-double-click on-double-click
:id label-id}
label]
@ -498,7 +500,7 @@
(when (fn? on-start-edition)
(on-start-edition v))))]
[:fieldset {:class (stl/css :sets-list)}
[:div {:class (stl/css :sets-list)}
(if ^boolean empty-state?
[:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message-sets)}
(tr "workspace.token.no-sets-create")]

View file

@ -70,6 +70,7 @@
}
.icon {
flex-shrink: 0;
display: flex;
align-items: center;
width: $s-20;
@ -82,6 +83,7 @@
}
.checkbox-style {
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;

View file

@ -40,6 +40,7 @@
[app.util.i18n :refer [tr]]
[app.util.webapi :as wapi]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]
@ -319,8 +320,7 @@
[:*
[:& token-context-menu]
[:& title-bar {:all-clickable true
:title (tr "workspace.token.tokens-section-title" selected-token-set-name)}]
[:span {:class (stl/css :sets-header)} (tr "workspace.token.tokens-section-title" selected-token-set-name)]
(for [type filled-group]
(let [tokens (get tokens-by-type type)]
@ -368,9 +368,10 @@
(fn [event]
(let [file (-> (dom/get-target event)
(dom/get-files)
(first))]
(first))
file-name (str/replace (.-name file) ".json" "")]
(->> (wapi/read-file-as-text file)
(sd/process-json-stream)
(sd/process-json-stream {:file-name file-name})
(rx/subs! (fn [lib]
(st/emit! (ptk/data-event ::ev/event {::ev/name "import-tokens"})
(dt/import-tokens-lib lib)))
@ -439,6 +440,7 @@
[:div {:class (stl/css :resize-area-horiz)
:on-pointer-down on-pointer-down-pages
:on-lost-pointer-capture on-lost-pointer-capture-pages
:on-pointer-move on-pointer-move-pages}]
:on-pointer-move on-pointer-move-pages}
[:div {:class (stl/css :resize-handle-horiz)}]]
[:> tokens-section* {:tokens-lib tokens-lib}]]
[:> import-export-button*]]))

View file

@ -38,12 +38,17 @@
padding-block-end: $s-16;
}
.themes-header {
.themes-header,
.sets-header {
@include use-typography("headline-small");
display: block;
margin-bottom: $s-8;
padding-left: $s-8;
padding: $s-8;
color: var(--title-foreground-color);
word-break: break-word;
}
.sets-header {
margin-block-start: $s-8;
}
.themes-wrapper {
@ -170,6 +175,12 @@
position: absolute;
left: 0;
width: 100%;
padding: $s-3 0 $s-1 0;
height: $s-6;
cursor: ns-resize;
}
.resize-handle-horiz {
border-bottom: $s-2 solid var(--resize-area-border-color);
cursor: ns-resize;
}

View file

@ -6,6 +6,7 @@
[app.common.schema :as sm]
[app.common.transit :as t]
[app.common.types.tokens-lib :as ctob]
[app.main.refs :as refs]
[app.main.ui.workspace.tokens.errors :as wte]
[app.main.ui.workspace.tokens.tinycolor :as tinycolor]
[app.main.ui.workspace.tokens.token :as wtt]
@ -249,33 +250,51 @@
(= header-2 "Reference Errors:"))
errors)))
(defn process-json-stream [data-stream]
(->> data-stream
(rx/map (fn [data]
(try
(t/decode-str data)
(catch js/Error e
(throw (wte/error-ex-info :error.import/json-parse-error data e))))))
(rx/map (fn [json-data]
(try
(if-not (ctob/has-legacy-format? json-data)
(ctob/decode-dtcg-json (ctob/ensure-tokens-lib nil) json-data)
(ctob/decode-legacy-json (ctob/ensure-tokens-lib nil) json-data))
(catch js/Error e
(throw (wte/error-ex-info :error.import/invalid-json-data json-data e))))))
(rx/mapcat (fn [tokens-lib]
(defn process-json-stream
([data-stream]
(process-json-stream nil data-stream))
([params data-stream]
(let [{:keys [file-name]} params]
(->> data-stream
(rx/map (fn [data]
(try
(-> (ctob/get-all-tokens tokens-lib)
(resolve-tokens-with-errors+)
(p/then (fn [_] tokens-lib))
(p/catch (fn [sd-error]
(let [reference-errors (reference-errors sd-error)
err (if reference-errors
(wte/error-ex-info :error.import/style-dictionary-reference-errors reference-errors sd-error)
(wte/error-ex-info :error.import/style-dictionary-unknown-error sd-error sd-error))]
(throw err)))))
(t/decode-str data)
(catch js/Error e
(p/rejected (wte/error-ex-info :error.import/style-dictionary-unknown-error "" e))))))))
(throw (wte/error-ex-info :error.import/json-parse-error data e))))))
(rx/map (fn [json-data]
(let [single-set? (ctob/single-set? json-data)
json-format (ctob/get-json-format json-data)]
(try
(cond
(and single-set?
(= :json-format/legacy json-format))
(ctob/decode-single-set-legacy-json (ctob/ensure-tokens-lib (deref refs/tokens-lib)) file-name json-data)
(and single-set?
(= :json-format/dtcg json-format))
(ctob/decode-single-set-json (ctob/ensure-tokens-lib (deref refs/tokens-lib)) file-name json-data)
(= :json-format/legacy json-format)
(ctob/decode-legacy-json (ctob/ensure-tokens-lib nil) json-data)
:else
(ctob/decode-dtcg-json (ctob/ensure-tokens-lib nil) json-data))
(catch js/Error e
(throw (wte/error-ex-info :error.import/invalid-json-data json-data e)))))))
(rx/mapcat (fn [tokens-lib]
(try
(-> (ctob/get-all-tokens tokens-lib)
(resolve-tokens-with-errors+)
(p/then (fn [_] tokens-lib))
(p/catch (fn [sd-error]
(let [reference-errors (reference-errors sd-error)
err (if reference-errors
(wte/error-ex-info :error.import/style-dictionary-reference-errors reference-errors sd-error)
(wte/error-ex-info :error.import/style-dictionary-unknown-error sd-error sd-error))]
(throw err)))))
(catch js/Error e
(p/rejected (wte/error-ex-info :error.import/style-dictionary-unknown-error "" e))))))))))
;; === Errors

View file

@ -41,7 +41,7 @@
:sub-item grouped?
:is-selected selected?)
:on-click select-theme}
[:> text* {:as "span" :typography "body-small" :class (stl/css :label)} name]
[:> text* {:as "span" :typography "body-small" :class (stl/css :label) :title name} name]
[:> icon* {:icon-id i/tick
:aria-hidden true
:class (stl/css-case :check-icon true
@ -58,7 +58,7 @@
:aria-labelledby (dm/str group "-label")
:role "group"}
(when (seq group)
[:> text* {:as "span" :typography "headline-small" :class (stl/css :group) :id (dm/str group "-label")} group])
[:> text* {:as "span" :typography "headline-small" :class (stl/css :group) :id (dm/str (str/kebab group) "-label") :title group} group])
[:& themes-list {:themes themes
:active-theme-paths active-theme-paths
:on-close on-close

View file

@ -41,6 +41,7 @@
}
.group {
@include textEllipsis;
display: block;
padding: $s-8;
color: var(--color-foreground-secondary);