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:
commit
076d64df8f
31 changed files with 339 additions and 141 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{"color":
|
||||
{"red":
|
||||
{"100":
|
||||
{"value":"red",
|
||||
"type":"color",
|
||||
"description":""}}}}
|
6
common/test/common_tests/types/data/single-set.json
Normal file
6
common/test/common_tests/types/data/single-set.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{"color":
|
||||
{"red":
|
||||
{"100":
|
||||
{"$value":"red",
|
||||
"$type":"color",
|
||||
"$description":""}}}}
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
|
|
|
@ -192,3 +192,5 @@
|
|||
:height 720}])
|
||||
|
||||
(def zoom-half-pixel-precision 8)
|
||||
|
||||
(def max-input-length 255)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]])]])))
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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}]])
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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*]]))
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
}
|
||||
|
||||
.group {
|
||||
@include textEllipsis;
|
||||
display: block;
|
||||
padding: $s-8;
|
||||
color: var(--color-foreground-secondary);
|
||||
|
|
Loading…
Add table
Reference in a new issue