From 54e7551d56a41f34df228dc63f9c1e3dc88c29d5 Mon Sep 17 00:00:00 2001
From: Andrey Antukh
Date: Fri, 10 Jan 2025 12:07:47 +0100
Subject: [PATCH 01/26] :bug: Backport comments decoding from develop
Mainly for backward compatibility with database
layout on comments tables from develop / v2.5
---
backend/src/app/rpc/commands/comments.clj | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/backend/src/app/rpc/commands/comments.clj b/backend/src/app/rpc/commands/comments.clj
index fafecd8b87..6ce530aa0e 100644
--- a/backend/src/app/rpc/commands/comments.clj
+++ b/backend/src/app/rpc/commands/comments.clj
@@ -29,10 +29,11 @@
;; --- GENERAL PURPOSE INTERNAL HELPERS
(defn- decode-row
- [{:keys [participants position] :as row}]
+ [{:keys [participants position mentions] :as row}]
(cond-> row
(db/pgpoint? position) (assoc :position (db/decode-pgpoint position))
- (db/pgobject? participants) (assoc :participants (db/decode-transit-pgobject participants))))
+ (db/pgobject? participants) (assoc :participants (db/decode-transit-pgobject participants))
+ (db/pgarray? mentions) (assoc :mentions (db/decode-pgarray mentions #{}))))
(def xf-decode-row
(map decode-row))
From 58dd23f9c72c10cc1dc6a4c0aa9c5d0388807aad Mon Sep 17 00:00:00 2001
From: "alonso.torres"
Date: Fri, 10 Jan 2025 12:15:14 +0100
Subject: [PATCH 02/26] :bug: Fix problem when changing color libraries
---
CHANGES.md | 1 +
.../app/main/data/workspace/libraries.cljs | 19 +++++++++++++++++++
frontend/src/app/plugins/library.cljs | 8 ++++----
3 files changed, 24 insertions(+), 4 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index b2811d57c9..520c2af07d 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -5,6 +5,7 @@
### :bug: Bugs fixed
- Fix error when importing files with touched components [Taiga #9625](https://tree.taiga.io/project/penpot/issue/9625)
+- Fix problem when changing color libraries [Plugins #184](https://github.com/penpot/penpot-plugins/issues/184)
## 2.4.0
diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs
index fa16fb9a8f..25893d81ba 100644
--- a/frontend/src/app/main/data/workspace/libraries.cljs
+++ b/frontend/src/app/main/data/workspace/libraries.cljs
@@ -191,6 +191,25 @@
(watch [it state _]
(update-color* it state color file-id)))))
+(defn update-color-data
+ "Update color data without affecting the path location"
+ [color file-id]
+ (let [color (d/without-nils color)]
+
+ (dm/assert!
+ "expected valid color data structure"
+ (ctc/check-color! color))
+
+ (dm/assert!
+ "expected file-id"
+ (uuid? file-id))
+
+ (ptk/reify ::update-color-data
+ ptk/WatchEvent
+ (watch [it state _]
+ (let [color (assoc color :name (dm/str (:path color) "/" (:name color)))]
+ (update-color* it state color file-id))))))
+
(defn rename-color
[file-id id new-name]
(dm/assert!
diff --git a/frontend/src/app/plugins/library.cljs b/frontend/src/app/plugins/library.cljs
index 29b8b15b0e..31241a4bf7 100644
--- a/frontend/src/app/plugins/library.cljs
+++ b/frontend/src/app/plugins/library.cljs
@@ -98,7 +98,7 @@
:else
(let [color (-> (u/proxy->library-color self)
(assoc :color value))]
- (st/emit! (dwl/update-color color file-id)))))}
+ (st/emit! (dwl/update-color-data color file-id)))))}
:opacity
{:this true
@@ -115,7 +115,7 @@
:else
(let [color (-> (u/proxy->library-color self)
(assoc :opacity value))]
- (st/emit! (dwl/update-color color file-id)))))}
+ (st/emit! (dwl/update-color-data color file-id)))))}
:gradient
{:this true
@@ -133,7 +133,7 @@
:else
(let [color (-> (u/proxy->library-color self)
(assoc :gradient value))]
- (st/emit! (dwl/update-color color file-id))))))}
+ (st/emit! (dwl/update-color-data color file-id))))))}
:image
{:this true
@@ -151,7 +151,7 @@
:else
(let [color (-> (u/proxy->library-color self)
(assoc :image value))]
- (st/emit! (dwl/update-color color file-id))))))}
+ (st/emit! (dwl/update-color-data color file-id))))))}
:remove
(fn []
From e380289e34a056481b47e04569ba58d6078273a6 Mon Sep 17 00:00:00 2001
From: Pablo Alba
Date: Fri, 10 Jan 2025 21:25:55 +0100
Subject: [PATCH 03/26] :bug: Fix feature flags not setting on login
---
frontend/src/app/config.cljs | 5 +++++
frontend/src/app/main/data/users.cljs | 1 +
2 files changed, 6 insertions(+)
diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs
index b809f7e2ec..13d3e8d88c 100644
--- a/frontend/src/app/config.cljs
+++ b/frontend/src/app/config.cljs
@@ -148,6 +148,11 @@
(let [f (obj/get global "externalContextInfo")]
(when (fn? f) (f))))
+(defn initialize-external-context-info
+ []
+ (let [f (obj/get global "initializeExternalConfigInfo")]
+ (when (fn? f) (f))))
+
;; --- Helper Functions
(defn ^boolean check-browser? [candidate]
diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs
index 237cdde463..a8562e06cf 100644
--- a/frontend/src/app/main/data/users.cljs
+++ b/frontend/src/app/main/data/users.cljs
@@ -207,6 +207,7 @@
ptk/WatchEvent
(watch [_ _ _]
(when (is-authenticated? profile)
+ (cf/initialize-external-context-info)
(->> (rx/concat
(rx/of (profile-fetched profile)
(fetch-teams)
From accc662e1c01b884f65733f7a521bcd65e801364 Mon Sep 17 00:00:00 2001
From: Pablo Alba
Date: Mon, 13 Jan 2025 19:13:36 +0100
Subject: [PATCH 04/26] :bug: Fix login is not shown on 404
---
frontend/src/app/main/ui/static.cljs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/app/main/ui/static.cljs b/frontend/src/app/main/ui/static.cljs
index 50a3141e81..d892d0fde4 100644
--- a/frontend/src/app/main/ui/static.cljs
+++ b/frontend/src/app/main/ui/static.cljs
@@ -476,7 +476,7 @@
request-access?
(and
- (= (:type data) :not-found)
+ (or (= (:type data) :not-found) (= (:type data) :authentication))
(or workspace? dashboard? view?)
(or (:file-id info)
(:team-id info)))]
From d7d7535ab4a122a44fcf31fc8f968c7d04b87bea Mon Sep 17 00:00:00 2001
From: AzazelN28
Date: Mon, 13 Jan 2025 15:05:33 +0100
Subject: [PATCH 05/26] :bug: Fix text editor copy/paste issue
---
.../src/editor/content/dom/Content.js | 34 ++-
.../editor/controllers/SelectionController.js | 19 +-
.../controllers/SelectionController.test.js | 245 ++++++++++++++++++
3 files changed, 295 insertions(+), 3 deletions(-)
diff --git a/frontend/text-editor/src/editor/content/dom/Content.js b/frontend/text-editor/src/editor/content/dom/Content.js
index 2c28f269fb..1b2ca84edd 100644
--- a/frontend/text-editor/src/editor/content/dom/Content.js
+++ b/frontend/text-editor/src/editor/content/dom/Content.js
@@ -6,7 +6,7 @@
* Copyright (c) KALEIDOS INC
*/
-import { createInline } from "./Inline.js";
+import { createInline, isLikeInline } from "./Inline.js";
import {
createEmptyParagraph,
createParagraph,
@@ -14,6 +14,31 @@ import {
} from "./Paragraph.js";
import { isDisplayBlock, normalizeStyles } from "./Style.js";
+/**
+ * Returns if the content fragment should be treated as
+ * inline content and not a paragraphed one.
+ *
+ * @returns {boolean}
+ */
+function isContentFragmentFromDocumentInline(document) {
+ const nodeIterator = document.createNodeIterator(
+ document.documentElement,
+ NodeFilter.SHOW_ELEMENT,
+ );
+ let currentNode = nodeIterator.nextNode();
+ while (currentNode) {
+ if (["HTML", "HEAD", "BODY"].includes(currentNode.nodeName)) {
+ currentNode = nodeIterator.nextNode();
+ continue;
+ }
+
+ if (!isLikeInline(currentNode)) return false;
+
+ currentNode = nodeIterator.nextNode();
+ }
+ return true;
+}
+
/**
* Maps any HTML into a valid content DOM element.
*
@@ -58,6 +83,13 @@ export function mapContentFragmentFromDocument(document, root, styleDefaults) {
}
fragment.appendChild(currentParagraph);
+ if (fragment.children.length === 1) {
+ const isContentInline = isContentFragmentFromDocumentInline(document);
+ if (isContentInline) {
+ currentParagraph.dataset.inline = "force";
+ }
+ }
+
return fragment;
}
diff --git a/frontend/text-editor/src/editor/controllers/SelectionController.js b/frontend/text-editor/src/editor/controllers/SelectionController.js
index 0329de9432..4f8e49ca9d 100644
--- a/frontend/text-editor/src/editor/controllers/SelectionController.js
+++ b/frontend/text-editor/src/editor/controllers/SelectionController.js
@@ -1034,7 +1034,23 @@ export class SelectionController extends EventTarget {
* @param {DocumentFragment} fragment
*/
insertPaste(fragment) {
- const numParagraphs = fragment.children.length;
+ if (fragment.children.length === 1
+ && fragment.firstElementChild?.dataset?.inline === "force"
+ ) {
+ if (this.isInlineStart) {
+ this.focusInline.before(...fragment.firstElementChild.children)
+ } else if (this.isInlineEnd) {
+ this.focusInline.after(...fragment.firstElementChild.children);
+ } else {
+ const newInline = splitInline(
+ this.focusInline,
+ this.focusOffset
+ )
+ this.focusInline.after(...fragment.firstElementChild.children, newInline)
+ }
+ return;
+ }
+
if (this.isParagraphStart) {
this.focusParagraph.before(fragment);
} else if (this.isParagraphEnd) {
@@ -1055,7 +1071,6 @@ export class SelectionController extends EventTarget {
* @param {DocumentFragment} fragment
*/
replaceWithPaste(fragment) {
- const numParagraphs = fragment.children.length;
this.removeSelected();
this.insertPaste(fragment);
}
diff --git a/frontend/text-editor/src/editor/controllers/SelectionController.test.js b/frontend/text-editor/src/editor/controllers/SelectionController.test.js
index 786c9a18de..c44989f692 100644
--- a/frontend/text-editor/src/editor/controllers/SelectionController.test.js
+++ b/frontend/text-editor/src/editor/controllers/SelectionController.test.js
@@ -246,6 +246,251 @@ describe("SelectionController", () => {
);
});
+ test("`insertPaste` should insert a paragraph from a pasted fragment (at start)", () => {
+ const textEditorMock =
+ TextEditorMock.createTextEditorMockWithText(", World!");
+ const root = textEditorMock.root;
+ const selection = document.getSelection();
+ const selectionController = new SelectionController(
+ textEditorMock,
+ selection,
+ );
+ focus(selection, textEditorMock, root.firstChild.firstChild.firstChild, 0);
+ const paragraph = createParagraph([createInline(new Text("Hello"))]);
+ const fragment = document.createDocumentFragment();
+ fragment.append(paragraph);
+
+ selectionController.insertPaste(fragment);
+ expect(textEditorMock.root).toBeInstanceOf(HTMLDivElement);
+ expect(textEditorMock.root.dataset.itype).toBe("root");
+ expect(textEditorMock.root.firstChild).toBeInstanceOf(HTMLDivElement);
+ expect(textEditorMock.root.firstChild.dataset.itype).toBe("paragraph");
+ expect(textEditorMock.root.firstChild.firstChild).toBeInstanceOf(
+ HTMLSpanElement,
+ );
+ expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe(
+ "inline",
+ );
+ expect(textEditorMock.root.textContent).toBe("Hello, World!");
+ expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf(
+ Text,
+ );
+ expect(textEditorMock.root.firstChild.firstChild.firstChild.nodeValue).toBe(
+ "Hello",
+ );
+ expect(
+ textEditorMock.root.lastChild.firstChild.firstChild.nodeValue,
+ ).toBe(", World!");
+ });
+
+ test("`insertPaste` should insert a paragraph from a pasted fragment (at middle)", () => {
+ const textEditorMock =
+ TextEditorMock.createTextEditorMockWithText("Lorem dolor");
+ const root = textEditorMock.root;
+ const selection = document.getSelection();
+ const selectionController = new SelectionController(
+ textEditorMock,
+ selection,
+ );
+ focus(selection, textEditorMock, root.firstChild.firstChild.firstChild, "Lorem ".length);
+ const paragraph = createParagraph([createInline(new Text("ipsum "))]);
+ const fragment = document.createDocumentFragment();
+ fragment.append(paragraph);
+
+ selectionController.insertPaste(fragment);
+ expect(textEditorMock.root).toBeInstanceOf(HTMLDivElement);
+ expect(textEditorMock.root.dataset.itype).toBe("root");
+ expect(textEditorMock.root.firstChild).toBeInstanceOf(HTMLDivElement);
+ expect(textEditorMock.root.firstChild.dataset.itype).toBe("paragraph");
+ expect(textEditorMock.root.firstChild.firstChild).toBeInstanceOf(
+ HTMLSpanElement,
+ );
+ expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe(
+ "inline",
+ );
+ expect(textEditorMock.root.textContent).toBe("Lorem ipsum dolor");
+ expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf(
+ Text,
+ );
+ expect(textEditorMock.root.firstChild.firstChild.firstChild.nodeValue).toBe(
+ "Lorem ",
+ );
+ expect(textEditorMock.root.children.item(1).firstChild.firstChild.nodeValue).toBe(
+ "ipsum ",
+ );
+ expect(textEditorMock.root.lastChild.firstChild.firstChild.nodeValue).toBe(
+ "dolor",
+ );
+ });
+
+ test("`insertPaste` should insert a paragraph from a pasted fragment (at end)", () => {
+ const textEditorMock = TextEditorMock.createTextEditorMockWithText("Hello");
+ const root = textEditorMock.root;
+ const selection = document.getSelection();
+ const selectionController = new SelectionController(
+ textEditorMock,
+ selection,
+ );
+ focus(
+ selection,
+ textEditorMock,
+ root.firstChild.firstChild.firstChild,
+ "Hello".length,
+ );
+ const paragraph = createParagraph([createInline(new Text(", World!"))]);
+ const fragment = document.createDocumentFragment();
+ fragment.append(paragraph);
+
+ selectionController.insertPaste(fragment);
+ expect(textEditorMock.root).toBeInstanceOf(HTMLDivElement);
+ expect(textEditorMock.root.dataset.itype).toBe("root");
+ expect(textEditorMock.root.firstChild).toBeInstanceOf(HTMLDivElement);
+ expect(textEditorMock.root.firstChild.dataset.itype).toBe("paragraph");
+ expect(textEditorMock.root.firstChild.firstChild).toBeInstanceOf(
+ HTMLSpanElement,
+ );
+ expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe(
+ "inline",
+ );
+ expect(textEditorMock.root.textContent).toBe("Hello, World!");
+ expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf(
+ Text,
+ );
+ expect(textEditorMock.root.firstChild.firstChild.firstChild.nodeValue).toBe(
+ "Hello",
+ );
+ expect(
+ textEditorMock.root.lastChild.firstChild.firstChild.nodeValue,
+ ).toBe(", World!");
+ });
+
+ test("`insertPaste` should insert an inline from a pasted fragment (at start)", () => {
+ const textEditorMock = TextEditorMock.createTextEditorMockWithText(", World!");
+ const root = textEditorMock.root;
+ const selection = document.getSelection();
+ const selectionController = new SelectionController(
+ textEditorMock,
+ selection,
+ );
+ focus(
+ selection,
+ textEditorMock,
+ root.firstChild.firstChild.firstChild,
+ 0,
+ );
+ const paragraph = createParagraph([createInline(new Text("Hello"))]);
+ paragraph.dataset.inline = "force";
+ const fragment = document.createDocumentFragment();
+ fragment.append(paragraph);
+
+ selectionController.insertPaste(fragment);
+ expect(textEditorMock.root).toBeInstanceOf(HTMLDivElement);
+ expect(textEditorMock.root.dataset.itype).toBe("root");
+ expect(textEditorMock.root.firstChild).toBeInstanceOf(HTMLDivElement);
+ expect(textEditorMock.root.firstChild.dataset.itype).toBe("paragraph");
+ expect(textEditorMock.root.firstChild.firstChild).toBeInstanceOf(
+ HTMLSpanElement,
+ );
+ expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe(
+ "inline",
+ );
+ expect(textEditorMock.root.textContent).toBe("Hello, World!");
+ expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf(
+ Text,
+ );
+ expect(textEditorMock.root.firstChild.firstChild.firstChild.nodeValue).toBe(
+ "Hello",
+ );
+ expect(
+ textEditorMock.root.firstChild.children.item(1).firstChild.nodeValue,
+ ).toBe(", World!");
+ });
+
+ test("`insertPaste` should insert an inline from a pasted fragment (at middle)", () => {
+ const textEditorMock =
+ TextEditorMock.createTextEditorMockWithText("Lorem dolor");
+ const root = textEditorMock.root;
+ const selection = document.getSelection();
+ const selectionController = new SelectionController(
+ textEditorMock,
+ selection,
+ );
+ focus(selection, textEditorMock, root.firstChild.firstChild.firstChild, "Lorem ".length);
+ const paragraph = createParagraph([createInline(new Text("ipsum "))]);
+ paragraph.dataset.inline = "force";
+ const fragment = document.createDocumentFragment();
+ fragment.append(paragraph);
+
+ selectionController.insertPaste(fragment);
+ expect(textEditorMock.root).toBeInstanceOf(HTMLDivElement);
+ expect(textEditorMock.root.dataset.itype).toBe("root");
+ expect(textEditorMock.root.firstChild).toBeInstanceOf(HTMLDivElement);
+ expect(textEditorMock.root.firstChild.dataset.itype).toBe("paragraph");
+ expect(textEditorMock.root.firstChild.firstChild).toBeInstanceOf(
+ HTMLSpanElement,
+ );
+ expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe(
+ "inline",
+ );
+ expect(textEditorMock.root.textContent).toBe("Lorem ipsum dolor");
+ expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf(
+ Text,
+ );
+ expect(textEditorMock.root.firstChild.firstChild.firstChild.nodeValue).toBe(
+ "Lorem ",
+ );
+ expect(textEditorMock.root.firstChild.children.item(1).firstChild.nodeValue).toBe(
+ "ipsum ",
+ );
+ expect(
+ textEditorMock.root.firstChild.children.item(2).firstChild.nodeValue,
+ ).toBe("dolor");
+ });
+
+ test("`insertPaste` should insert an inline from a pasted fragment (at end)", () => {
+ const textEditorMock = TextEditorMock.createTextEditorMockWithText("Hello");
+ const root = textEditorMock.root;
+ const selection = document.getSelection();
+ const selectionController = new SelectionController(
+ textEditorMock,
+ selection,
+ );
+ focus(
+ selection,
+ textEditorMock,
+ root.firstChild.firstChild.firstChild,
+ "Hello".length,
+ );
+ const paragraph = createParagraph([
+ createInline(new Text(", World!"))
+ ]);
+ paragraph.dataset.inline = "force";
+ const fragment = document.createDocumentFragment();
+ fragment.append(paragraph);
+
+ selectionController.insertPaste(fragment);
+ expect(textEditorMock.root).toBeInstanceOf(HTMLDivElement);
+ expect(textEditorMock.root.dataset.itype).toBe("root");
+ expect(textEditorMock.root.firstChild).toBeInstanceOf(HTMLDivElement);
+ expect(textEditorMock.root.firstChild.dataset.itype).toBe("paragraph");
+ expect(textEditorMock.root.firstChild.firstChild).toBeInstanceOf(
+ HTMLSpanElement,
+ );
+ expect(textEditorMock.root.firstChild.firstChild.dataset.itype).toBe(
+ "inline",
+ );
+ expect(textEditorMock.root.textContent).toBe("Hello, World!");
+ expect(textEditorMock.root.firstChild.firstChild.firstChild).toBeInstanceOf(
+ Text,
+ );
+ expect(textEditorMock.root.firstChild.firstChild.firstChild.nodeValue).toBe(
+ "Hello",
+ );
+ expect(textEditorMock.root.firstChild.children.item(1).firstChild.nodeValue).toBe(
+ ", World!",
+ );
+ });
+
test("`removeBackwardText` should remove text in backward direction (backspace)", () => {
const textEditorMock =
TextEditorMock.createTextEditorMockWithText("Hello, World!");
From e92ddee33a6ce45826a839f01fabc7e3717b3f8e Mon Sep 17 00:00:00 2001
From: Andrey Antukh
Date: Tue, 14 Jan 2025 17:26:58 +0100
Subject: [PATCH 06/26] :whale: Move devenv secret key env asignation to
scripts
from the docker compose
---
backend/scripts/repl | 1 +
backend/scripts/start-dev | 1 +
docker/devenv/docker-compose.yaml | 1 -
3 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/backend/scripts/repl b/backend/scripts/repl
index 4aa78f0250..1540e36016 100755
--- a/backend/scripts/repl
+++ b/backend/scripts/repl
@@ -1,5 +1,6 @@
#!/usr/bin/env bash
+export PENPOT_SECRET_KEY=super-secret-devenv-key
export PENPOT_HOST=devenv
export PENPOT_FLAGS="\
$PENPOT_FLAGS \
diff --git a/backend/scripts/start-dev b/backend/scripts/start-dev
index 4e4c8497fb..9fe2ccb1b4 100755
--- a/backend/scripts/start-dev
+++ b/backend/scripts/start-dev
@@ -1,5 +1,6 @@
#!/usr/bin/env bash
+export PENPOT_SECRET_KEY=super-secret-devenv-key
export PENPOT_HOST=devenv
export PENPOT_FLAGS="\
$PENPOT_FLAGS \
diff --git a/docker/devenv/docker-compose.yaml b/docker/devenv/docker-compose.yaml
index d7b5da48a5..82a3c0ad76 100644
--- a/docker/devenv/docker-compose.yaml
+++ b/docker/devenv/docker-compose.yaml
@@ -43,7 +43,6 @@ services:
environment:
- EXTERNAL_UID=${CURRENT_USER_ID}
- - PENPOT_SECRET_KEY=super-secret-devenv-key
# SMTP setup
- PENPOT_SMTP_ENABLED=true
- PENPOT_SMTP_DEFAULT_FROM=no-reply@example.com
From 5c428b5aa507cff8c3666a7c565c2e0d688c7476 Mon Sep 17 00:00:00 2001
From: Andrey Antukh
Date: Tue, 14 Jan 2025 17:41:28 +0100
Subject: [PATCH 07/26] :bug: Fix repeated password update on login
because the default options were not being passed in the verification
---
backend/src/app/auth.clj | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/backend/src/app/auth.clj b/backend/src/app/auth.clj
index fc6d254810..271e52e024 100644
--- a/backend/src/app/auth.clj
+++ b/backend/src/app/auth.clj
@@ -8,7 +8,7 @@
(:require
[buddy.hashers :as hashers]))
-(def default-params
+(def ^:private default-options
{:alg :argon2id
:memory 32768 ;; 32 MiB
:iterations 3
@@ -16,12 +16,12 @@
(defn derive-password
[password]
- (hashers/derive password default-params))
+ (hashers/derive password default-options))
(defn verify-password
[attempt password]
(try
- (hashers/verify attempt password)
+ (hashers/verify attempt password default-options)
(catch Throwable _
{:update false
:valid false})))
From 40693e6857ed375e04d60480f3cb290c700d95b1 Mon Sep 17 00:00:00 2001
From: Andrey Antukh
Date: Tue, 14 Jan 2025 17:42:49 +0100
Subject: [PATCH 08/26] :bug: Make the PENPOT_SECRET_KEY optional
Fix a regression introduced with 2.4
---
backend/src/app/setup.clj | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/backend/src/app/setup.clj b/backend/src/app/setup.clj
index 8e2733c6df..6df3ce657a 100644
--- a/backend/src/app/setup.clj
+++ b/backend/src/app/setup.clj
@@ -74,8 +74,7 @@
(defmethod ig/assert-key ::props
[_ params]
- (assert (db/pool? (::db/pool params)) "expected valid database pool")
- (assert (string? (::key params)) "expected valid key string"))
+ (assert (db/pool? (::db/pool params)) "expected valid database pool"))
(defmethod ig/init-key ::props
[_ {:keys [::db/pool ::key] :as cfg}]
From 5247d217abee5d545c15138bfb037e10a3dba0da Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9s=20Moya?=
Date: Wed, 15 Jan 2025 12:48:46 +0100
Subject: [PATCH 09/26] :bug: Fix detach when top copy is dangling and nested
copy is not
---
CHANGES.md | 6 ++
common/src/app/common/logic/libraries.cljc | 7 +-
common/src/app/common/types/file.cljc | 18 +++--
common/src/app/common/types/shape.cljc | 4 +-
.../logic/comp_detach_with_nested_test.cljc | 77 +++++++++++++++++++
5 files changed, 102 insertions(+), 10 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index 520c2af07d..a138453cc2 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,11 @@
# CHANGELOG
+## 2.4.2
+
+### :bug: Bugs fixed
+
+- Fix detach when top copy is dangling and nested copy is not [Taiga #9699](https://tree.taiga.io/project/penpot/issue/9699)
+
## 2.4.1
### :bug: Bugs fixed
diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc
index bc9ab68b02..669fdbd633 100644
--- a/common/src/app/common/logic/libraries.cljc
+++ b/common/src/app/common/logic/libraries.cljc
@@ -304,7 +304,12 @@
(and (some? (ctk/get-swap-slot ref-shape))
(nil? (ctk/get-swap-slot shape))
(not= (:id shape) shape-id))
- (pcb/update-shapes [(:id shape)] #(ctk/set-swap-slot % (ctk/get-swap-slot ref-shape))))))]
+ (pcb/update-shapes [(:id shape)] #(ctk/set-swap-slot % (ctk/get-swap-slot ref-shape)))
+
+ ;; If we can't get the ref-shape (e.g. it's in an external library not linked),
+ ;: we can't do a suitable advance. So it's better to detach the shape
+ (nil? ref-shape)
+ (pcb/update-shapes [(:id shape)] ctk/detach-shape))))]
(reduce skip-near changes children)))
diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc
index 739bca6d19..8236631ef8 100644
--- a/common/src/app/common/types/file.cljc
+++ b/common/src/app/common/types/file.cljc
@@ -738,16 +738,20 @@
(:component-id shape) "@"
:else "-")
- (when (and (:component-file shape) component-file)
+ (when (:component-file shape)
(str/format "<%s> "
- (if (= (:id component-file) (:id file))
- "local"
- (:name component-file))))
+ (if component-file
+ (if (= (:id component-file) (:id file))
+ "local"
+ (:name component-file))
+ (if show-ids
+ (str/format "¿%s?" (:component-file shape))
+ "?"))))
(or (:name component-shape)
- (str/format "?%s"
- (when show-ids
- (str " " (:shape-ref shape)))))
+ (if show-ids
+ (str/format "¿%s?" (:shape-ref shape))
+ "?"))
(when (and show-ids component-shape)
(str/format " %s" (:id component-shape)))
diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc
index fa0de53597..b483c5fe4c 100644
--- a/common/src/app/common/types/shape.cljc
+++ b/common/src/app/common/types/shape.cljc
@@ -154,6 +154,7 @@
[:main-instance {:optional true} :boolean]
[:remote-synced {:optional true} :boolean]
[:shape-ref {:optional true} ::sm/uuid]
+ [:touched {:optional true} [:maybe [:set :keyword]]]
[:blocked {:optional true} :boolean]
[:collapsed {:optional true} :boolean]
[:locked {:optional true} :boolean]
@@ -191,8 +192,7 @@
[:grow-type {:optional true}
[::sm/one-of grow-types]]
[:applied-tokens {:optional true} ::cto/applied-tokens]
- [:plugin-data {:optional true} ::ctpg/plugin-data]
- [:touched {:optional true} [:maybe [:set :keyword]]]])
+ [:plugin-data {:optional true} ::ctpg/plugin-data]])
(def schema:group-attrs
[:map {:title "GroupAttrs"}
diff --git a/common/test/common_tests/logic/comp_detach_with_nested_test.cljc b/common/test/common_tests/logic/comp_detach_with_nested_test.cljc
index d7b999db31..ac7d62d116 100644
--- a/common/test/common_tests/logic/comp_detach_with_nested_test.cljc
+++ b/common/test/common_tests/logic/comp_detach_with_nested_test.cljc
@@ -103,6 +103,83 @@
(t/is (= (:shape-ref copy-nested-ellipse) (thi/id :nested-ellipse)))
(t/is (nil? (ctk/get-swap-slot copy-nested-ellipse)))))
+(t/deftest test-advance-in-library
+ (let [;; ==== Setup
+ library (setup-file)
+ file (-> (thf/sample-file :file2)
+ (thc/instantiate-component :c-big-board
+ :copy-big-board
+ :library library
+ :children-labels [:copy-h-board-with-ellipse
+ :copy-nested-h-ellipse
+ :copy-nested-ellipse]))
+ page (thf/current-page file)
+
+ ;; ==== Action
+ changes (cll/generate-detach-instance (-> (pcb/empty-changes nil)
+ (pcb/with-page page)
+ (pcb/with-objects (:objects page)))
+ page
+ {(:id file) file
+ (:id library) library}
+ (thi/id :copy-big-board))
+ file' (thf/apply-changes file changes)
+
+ ;; ==== Get
+ copy-h-board-with-ellipse (ths/get-shape file' :copy-h-board-with-ellipse)
+ copy-nested-h-ellipse (ths/get-shape file' :copy-nested-h-ellipse)
+ copy-nested-ellipse (ths/get-shape file' :copy-nested-ellipse)]
+
+ ;; ==== Check
+
+ ;; It should the same as above, but in an external library.
+ (thf/dump-file file)
+ (t/is (ctk/instance-root? copy-h-board-with-ellipse))
+ (t/is (= (:shape-ref copy-h-board-with-ellipse) (thi/id :board-with-ellipse)))
+ (t/is (nil? (ctk/get-swap-slot copy-h-board-with-ellipse)))
+
+ (t/is (ctk/instance-head? copy-nested-h-ellipse))
+ (t/is (= (:shape-ref copy-nested-h-ellipse) (thi/id :nested-h-ellipse)))
+ (t/is (nil? (ctk/get-swap-slot copy-nested-h-ellipse)))
+
+ (t/is (not (ctk/instance-head? copy-nested-ellipse)))
+ (t/is (= (:shape-ref copy-nested-ellipse) (thi/id :nested-ellipse)))
+ (t/is (nil? (ctk/get-swap-slot copy-nested-ellipse)))))
+
+(t/deftest test-advance-in-broken-library
+ (let [;; ==== Setup
+ library (setup-file)
+ file (-> (thf/sample-file :file2)
+ (thc/instantiate-component :c-big-board
+ :copy-big-board
+ :library library
+ :children-labels [:copy-h-board-with-ellipse
+ :copy-nested-h-ellipse
+ :copy-nested-ellipse]))
+ page (thf/current-page file)
+
+ ;; ==== Action
+ changes (cll/generate-detach-instance (-> (pcb/empty-changes nil)
+ (pcb/with-page page)
+ (pcb/with-objects (:objects page)))
+ page
+ {(:id file) file}
+ (thi/id :copy-big-board))
+ file' (thf/apply-changes file changes)
+
+ ;; ==== Get
+ copy-h-board-with-ellipse (ths/get-shape file' :copy-h-board-with-ellipse)
+ copy-nested-h-ellipse (ths/get-shape file' :copy-nested-h-ellipse)
+ copy-nested-ellipse (ths/get-shape file' :copy-nested-ellipse)]
+
+ ;; ==== Check
+
+ ;; If the main component cannot be found, because it's in a library that is
+ ;; not available, the nested copies should be detached too.
+ (t/is (not (ctk/in-component-copy? copy-h-board-with-ellipse)))
+ (t/is (not (ctk/in-component-copy? copy-nested-h-ellipse)))
+ (t/is (not (ctk/in-component-copy? copy-nested-ellipse)))))
+
(t/deftest test-dont-advance-when-swapped-copy
(let [;; ==== Setup
file (-> (setup-file)
From 423c237d427fdba92afa3b075bdf10ac1d51c801 Mon Sep 17 00:00:00 2001
From: Alejandro Alonso
Date: Fri, 17 Jan 2025 11:57:44 +0100
Subject: [PATCH 10/26] :bug: Fix reply to comment
---
backend/src/app/rpc/commands/comments.clj | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/backend/src/app/rpc/commands/comments.clj b/backend/src/app/rpc/commands/comments.clj
index 6ce530aa0e..7c44a8e671 100644
--- a/backend/src/app/rpc/commands/comments.clj
+++ b/backend/src/app/rpc/commands/comments.clj
@@ -462,8 +462,9 @@
:thread-id thread-id
:owner-id profile-id
:content content})
- props {:file-id file-id
- :share-id nil}]
+ comment (decode-row comment)
+ props {:file-id file-id
+ :share-id nil}]
;; Update thread modified-at attribute and assoc the current
;; profile to the participant set.
From a7a49e4b398a1a8a18da62dd3c2864aa8b500b3e Mon Sep 17 00:00:00 2001
From: Madalena Melo <144885032+madalenapmelo-kp@users.noreply.github.com>
Date: Tue, 26 Nov 2024 13:06:08 +0100
Subject: [PATCH 11/26] Viewer Role - Update index.njk
Add viewer role to the team roles; also made some tweaks to the descriptions of the other roles
---
docs/user-guide/teams/index.njk | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/docs/user-guide/teams/index.njk b/docs/user-guide/teams/index.njk
index 96d7968133..a72adefa17 100644
--- a/docs/user-guide/teams/index.njk
+++ b/docs/user-guide/teams/index.njk
@@ -36,9 +36,10 @@ member is allowed to do depends on their permissions.
Team roles
These are the team roles currently available at Penpot:
- Owner: There's only one owner per team, the role is automatically assigned to the team creator. Owners have permissions to change every other member role, including transfering ownership. Owners can update team settings, invite members and delete teams.
- Admin: Permissions to change every other member role except owners. Can invite members and update team settings.
- Editor: Without permissions to change member roles, invite members or update team settings.
+ Owner: There's only one owner per team, the role is automatically assigned to the team creator. Owners have permissions to change every other member's role, including transfering ownership. Owners can update team settings, invite members and delete teams.
+ Admin: Admins can change every other member's role except owners. They can invite members and update team settings.
+ Editor: Editors can edit and create files and projects, but do not have permissions to change member roles, invite members or update team settings.
+ Viewer: Viewers can view projects and files but will not be able to edit them; they also do not have permissions to change member roles, invite members or update team settings.
More team roles will be eventually available, as well as fine grained permissions management to control members access and actions.
From 91b0c4724485cb955d0691432f756521e064953f Mon Sep 17 00:00:00 2001
From: Madalena Melo <144885032+madalenapmelo-kp@users.noreply.github.com>
Date: Tue, 3 Dec 2024 15:50:35 +0100
Subject: [PATCH 12/26] Add detail to role descriptions
Added more context to each role's description; I tried to keep it brief while including more information about what each role can do both within the team as well as in terms of team management
---
docs/user-guide/teams/index.njk | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/user-guide/teams/index.njk b/docs/user-guide/teams/index.njk
index a72adefa17..45b216e1b3 100644
--- a/docs/user-guide/teams/index.njk
+++ b/docs/user-guide/teams/index.njk
@@ -36,10 +36,10 @@ member is allowed to do depends on their permissions.
Team roles
These are the team roles currently available at Penpot:
- Owner: There's only one owner per team, the role is automatically assigned to the team creator. Owners have permissions to change every other member's role, including transfering ownership. Owners can update team settings, invite members and delete teams.
- Admin: Admins can change every other member's role except owners. They can invite members and update team settings.
- Editor: Editors can edit and create files and projects, but do not have permissions to change member roles, invite members or update team settings.
- Viewer: Viewers can view projects and files but will not be able to edit them; they also do not have permissions to change member roles, invite members or update team settings.
+ Owner: There's only one owner per team, the role is automatically assigned to the team creator. Owners have full access to the team's projects and permissions to change every other member's role, including transfering ownership. Owners can update team settings, invite members and delete teams.
+ Admin: Admins have full access to the team's projects and can change every other member's role except owners. They can invite members and update team settings.
+ Editor: Editors can create, import, edit and manage files and libraries, but do not have permissions to manage team settings.
+ Viewer: Viewers can view, comment on and inspect files but will not be able to edit them; they also do not have permissions to manage team settings.
More team roles will be eventually available, as well as fine grained permissions management to control members access and actions.
From 4f1d5a19e4abcc5336c0f2d1be1424c351272b08 Mon Sep 17 00:00:00 2001
From: Madalena Melo <144885032+madalenapmelo-kp@users.noreply.github.com>
Date: Wed, 4 Dec 2024 12:09:34 +0100
Subject: [PATCH 13/26] Change the order to add clarity to admin and owner
roles
Switched the order of the roles to make it more logical and add more clarity about admins and owners ability to edit
---
docs/user-guide/teams/index.njk | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/user-guide/teams/index.njk b/docs/user-guide/teams/index.njk
index 45b216e1b3..47920629d0 100644
--- a/docs/user-guide/teams/index.njk
+++ b/docs/user-guide/teams/index.njk
@@ -36,10 +36,10 @@ member is allowed to do depends on their permissions.
Team roles
These are the team roles currently available at Penpot:
- Owner: There's only one owner per team, the role is automatically assigned to the team creator. Owners have full access to the team's projects and permissions to change every other member's role, including transfering ownership. Owners can update team settings, invite members and delete teams.
- Admin: Admins have full access to the team's projects and can change every other member's role except owners. They can invite members and update team settings.
+ Viewer: Viewers can view, comment on and inspect files but will not be able to edit them, nor do they have permissions to manage team settings.
Editor: Editors can create, import, edit and manage files and libraries, but do not have permissions to manage team settings.
- Viewer: Viewers can view, comment on and inspect files but will not be able to edit them; they also do not have permissions to manage team settings.
+ Admin: Admins have the same permissions as editors, with the added ability to change every other member's role except owners. They can invite members and update team settings.
+ Owner: There's only one owner per team, the role is automatically assigned to the team creator. Owners have all the permissions of admins, with the additional ability to change any member's role, including transferring ownership. Owners can update team settings, invite members and delete teams.
More team roles will be eventually available, as well as fine grained permissions management to control members access and actions.
From 1f0e470419331cecb37ce6b76bc9e3f2f67b3e56 Mon Sep 17 00:00:00 2001
From: Pablo Alba
Date: Mon, 20 Jan 2025 11:06:25 +0100
Subject: [PATCH 14/26] =?UTF-8?q?Revert=20":tada:=20Add=20A/B=20test=20of?=
=?UTF-8?q?=20use=20of=20boards=20if=20we=20just=20change=20the=20icon=20f?=
=?UTF-8?q?or=20=E2=80=9Cstandard=E2=80=9D=20one"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This reverts commit 0c586551c4810085aeb466a53c873755d2e8b5ad.
---
frontend/resources/images/icons/board-2.svg | 3 ---
frontend/src/app/main/ui/components/shape_icon.cljs | 5 ++---
frontend/src/app/main/ui/icons.cljs | 1 -
frontend/src/app/main/ui/workspace/sidebar/history.cljs | 3 +--
frontend/src/app/main/ui/workspace/sidebar/layers.cljs | 3 +--
frontend/src/app/main/ui/workspace/top_toolbar.cljs | 3 +--
6 files changed, 5 insertions(+), 13 deletions(-)
delete mode 100644 frontend/resources/images/icons/board-2.svg
diff --git a/frontend/resources/images/icons/board-2.svg b/frontend/resources/images/icons/board-2.svg
deleted file mode 100644
index 70a44ea155..0000000000
--- a/frontend/resources/images/icons/board-2.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/frontend/src/app/main/ui/components/shape_icon.cljs b/frontend/src/app/main/ui/components/shape_icon.cljs
index 86ce8f3bfe..060213681f 100644
--- a/frontend/src/app/main/ui/components/shape_icon.cljs
+++ b/frontend/src/app/main/ui/components/shape_icon.cljs
@@ -9,7 +9,6 @@
[app.common.types.component :as ctk]
[app.common.types.shape :as cts]
[app.common.types.shape.layout :as ctl]
- [app.config :as cf]
[app.main.ui.icons :as i]
[rumext.v2 :as mf]))
@@ -32,7 +31,7 @@
i/flex-grid
:else
- (if (cf/external-feature-flag "boards-01" "test") i/board-2 i/board))
+ i/board)
;; TODO -> THUMBNAIL ICON
:image i/img
:line (if (cts/has-images? shape) i/img i/path)
@@ -57,7 +56,7 @@
(if main-instance?
i/component
(case type
- :frame (if (cf/external-feature-flag "boards-01" "test") i/board-2 i/board)
+ :frame i/board
:image i/img
:shape i/path
:text i/text
diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs
index 29b0575572..c73c7d1bd9 100644
--- a/frontend/src/app/main/ui/icons.cljs
+++ b/frontend/src/app/main/ui/icons.cljs
@@ -63,7 +63,6 @@
(def ^:icon arrow (icon-xref :arrow))
(def ^:icon asc-sort (icon-xref :asc-sort))
(def ^:icon board (icon-xref :board))
-(def ^:icon board-2 (icon-xref :board-2))
(def ^:icon boards-thumbnail (icon-xref :boards-thumbnail))
(def ^:icon boolean-difference (icon-xref :boolean-difference))
(def ^:icon boolean-exclude (icon-xref :boolean-exclude))
diff --git a/frontend/src/app/main/ui/workspace/sidebar/history.cljs b/frontend/src/app/main/ui/workspace/sidebar/history.cljs
index 753c99c3c4..618e397b0d 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/history.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/history.cljs
@@ -9,7 +9,6 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
- [app.config :as cf]
[app.main.data.workspace.undo :as dwu]
[app.main.refs :as refs]
[app.main.store :as st]
@@ -155,7 +154,7 @@
:circle i/elipse
:text i/text
:path i/path
- :frame (if (cf/external-feature-flag "boards-01" "test") i/board-2 i/board)
+ :frame i/board
:group i/group
:color i/drop-icon
:typography i/text-palette
diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs
index 9dd8d234a2..88f3c46010 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs
@@ -12,7 +12,6 @@
[app.common.files.helpers :as cfh]
[app.common.types.shape :as cts]
[app.common.uuid :as uuid]
- [app.config :as cf]
[app.main.data.workspace :as dw]
[app.main.refs :as refs]
[app.main.store :as st]
@@ -336,7 +335,7 @@
:on-click add-filter}
[:div {:class (stl/css :filter-menu-item-name-wrapper)}
[:span {:class (stl/css :filter-menu-item-icon)}
- (if (cf/external-feature-flag "boards-01" "test") i/board-2 i/board)]
+ i/board]
[:span {:class (stl/css :filter-menu-item-name)}
(tr "workspace.sidebar.layers.frames")]]
diff --git a/frontend/src/app/main/ui/workspace/top_toolbar.cljs b/frontend/src/app/main/ui/workspace/top_toolbar.cljs
index eb19fc6f7d..fb9f3473cf 100644
--- a/frontend/src/app/main/ui/workspace/top_toolbar.cljs
+++ b/frontend/src/app/main/ui/workspace/top_toolbar.cljs
@@ -10,7 +10,6 @@
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.media :as cm]
- [app.config :as cf]
[app.main.data.events :as ev]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
@@ -147,7 +146,7 @@
:on-click select-drawtool
:data-tool "frame"
:data-testid "artboard-btn"}
- (if (cf/external-feature-flag "boards-01" "test") i/board-2 i/board)]]
+ i/board]]
[:li
[:button
{:title (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect))
From e2900d9012cae335ef0534db55c886c2adf5616e Mon Sep 17 00:00:00 2001
From: Pablo Alba
Date: Mon, 20 Jan 2025 11:07:13 +0100
Subject: [PATCH 15/26] :tada: Change of boards icon
---
frontend/resources/images/icons/board.svg | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frontend/resources/images/icons/board.svg b/frontend/resources/images/icons/board.svg
index d4e8525c2e..70a44ea155 100644
--- a/frontend/resources/images/icons/board.svg
+++ b/frontend/resources/images/icons/board.svg
@@ -1,3 +1,3 @@
-
-
\ No newline at end of file
+
+
From 96947b021908041da5e5a00f7f584843d9416ac7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marina=20L=C3=B3pez?=
Date: Mon, 20 Jan 2025 11:10:38 +0100
Subject: [PATCH 16/26] :sparkles: A/B test switching '+' to 'Add file' in an
empty project
---
.../app/main/ui/dashboard/placeholder.cljs | 3 +-
.../app/main/ui/dashboard/placeholder.scss | 1 -
frontend/translations/en.po | 1050 +++++++++--------
frontend/translations/es.po | 4 +
4 files changed, 539 insertions(+), 519 deletions(-)
diff --git a/frontend/src/app/main/ui/dashboard/placeholder.cljs b/frontend/src/app/main/ui/dashboard/placeholder.cljs
index 08e1b150dd..dc85232207 100644
--- a/frontend/src/app/main/ui/dashboard/placeholder.cljs
+++ b/frontend/src/app/main/ui/dashboard/placeholder.cljs
@@ -7,6 +7,7 @@
(ns app.main.ui.dashboard.placeholder
(:require-macros [app.main.style :as stl])
(:require
+ [app.config :as cf]
[app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]]
[app.main.ui.ds.product.loader :refer [loader*]]
[app.main.ui.icons :as i]
@@ -44,7 +45,7 @@
[:div {:class (stl/css :grid-empty-placeholder)}
[:button {:class (stl/css :create-new)
:on-click on-click}
- i/add]])))
+ (if (cf/external-feature-flag "add-file-01" "test") (tr "dashboard.add-file") i/add)]])))
(mf/defc loading-placeholder
[]
diff --git a/frontend/src/app/main/ui/dashboard/placeholder.scss b/frontend/src/app/main/ui/dashboard/placeholder.scss
index da06dd8635..9a4e89c1e7 100644
--- a/frontend/src/app/main/ui/dashboard/placeholder.scss
+++ b/frontend/src/app/main/ui/dashboard/placeholder.scss
@@ -48,7 +48,6 @@
cursor: pointer;
height: $s-160;
margin: $s-8;
- text-transform: uppercase;
border: $s-2 solid transparent;
width: var(--th-width, #{g.$thumbnail-default-width});
height: var(--th-height, #{g.$thumbnail-default-height});
diff --git a/frontend/translations/en.po b/frontend/translations/en.po
index 2c0b4b84a7..4029acdd2d 100644
--- a/frontend/translations/en.po
+++ b/frontend/translations/en.po
@@ -170,7 +170,7 @@ msgstr "The open-source solution for design and prototyping."
msgid "auth.terms-and-privacy-agreement"
msgstr "I agree to the [terms of service](%s) and [privacy policy](%s)."
-#: src/app/main/ui/auth/register.cljs:290, src/app/main/ui/dashboard/sidebar.cljs:1022, src/app/main/ui/workspace/main_menu.cljs:154
+#: src/app/main/ui/auth/register.cljs:290, src/app/main/ui/dashboard/sidebar.cljs:1022, src/app/main/ui/workspace/main_menu.cljs:155
msgid "auth.terms-of-service"
msgstr "Terms of service"
@@ -193,7 +193,7 @@ msgstr "Work email"
msgid "branding-illustrations-marketing-pieces"
msgstr "...branding, illustrations, marketing pieces, etc."
-#: src/app/main/ui/workspace/libraries.cljs:229
+#: src/app/main/ui/workspace/libraries.cljs:280
msgid "common.publish"
msgstr "Publish"
@@ -265,7 +265,7 @@ msgstr "Share prototypes"
msgid "common.share-link.view-all"
msgstr "Select All"
-#: src/app/main/ui/workspace/libraries.cljs:225
+#: src/app/main/ui/workspace/libraries.cljs:276
msgid "common.unpublish"
msgstr "Unpublish"
@@ -392,22 +392,19 @@ msgstr "The token will expire on %s"
msgid "dashboard.access-tokens.token-will-not-expire"
msgstr "The token has no expiration date"
-#: src/app/main/ui/dashboard/file_menu.cljs:311, src/app/main/ui/workspace/main_menu.cljs:585
+#: src/app/main/ui/dashboard/placeholder.cljs:48
+msgid "dashboard.add-file"
+msgstr "Add file"
+
+#: src/app/main/ui/dashboard/file_menu.cljs:300, src/app/main/ui/workspace/main_menu.cljs:614
msgid "dashboard.add-shared"
msgstr "Add as Shared Library"
-#: src/app/main/ui/workspace/main_menu.cljs:607
-msgid "dashboard.show-version-history"
-msgstr "Version history"
-
-msgid "dashboard.create-version-menu"
-msgstr "Pin this version"
-
#: src/app/main/ui/settings/profile.cljs:72
msgid "dashboard.change-email"
msgstr "Change email"
-#: src/app/main/data/dashboard.cljs:771, src/app/main/data/dashboard.cljs:991
+#: src/app/main/data/dashboard.cljs:773, src/app/main/data/dashboard.cljs:993
msgid "dashboard.copy-suffix"
msgstr "(copy)"
@@ -415,6 +412,10 @@ msgstr "(copy)"
msgid "dashboard.create-new-team"
msgstr "Create new team"
+#: src/app/main/ui/workspace/main_menu.cljs:625
+msgid "dashboard.create-version-menu"
+msgstr "Pin this version"
+
#: src/app/main/ui/components/context_menu_a11y.cljs:284, src/app/main/ui/dashboard/sidebar.cljs:646
msgid "dashboard.default-team-name"
msgstr "Your Penpot"
@@ -423,19 +424,19 @@ msgstr "Your Penpot"
msgid "dashboard.delete-team"
msgstr "Delete team"
-#: src/app/main/ui/dashboard/file_menu.cljs:318, src/app/main/ui/dashboard/file_menu.cljs:323, src/app/main/ui/workspace/main_menu.cljs:603, src/app/main/ui/workspace/main_menu.cljs:612
+#: src/app/main/ui/dashboard/file_menu.cljs:307, src/app/main/ui/dashboard/file_menu.cljs:312, src/app/main/ui/workspace/main_menu.cljs:652, src/app/main/ui/workspace/main_menu.cljs:661
msgid "dashboard.download-binary-file"
msgstr "Download Penpot file (.penpot)"
-#: src/app/main/ui/dashboard/file_menu.cljs:328, src/app/main/ui/workspace/main_menu.cljs:621
+#: src/app/main/ui/dashboard/file_menu.cljs:317, src/app/main/ui/workspace/main_menu.cljs:670
msgid "dashboard.download-standard-file"
msgstr "Download standard file (.svg + .json)"
-#: src/app/main/ui/dashboard/file_menu.cljs:293, src/app/main/ui/dashboard/project_menu.cljs:91
+#: src/app/main/ui/dashboard/file_menu.cljs:282, src/app/main/ui/dashboard/project_menu.cljs:91
msgid "dashboard.duplicate"
msgstr "Duplicate"
-#: src/app/main/ui/dashboard/file_menu.cljs:249
+#: src/app/main/ui/dashboard/file_menu.cljs:238
msgid "dashboard.duplicate-multi"
msgstr "Duplicate %s files"
@@ -455,7 +456,7 @@ msgstr "Once a project member creates a file, it will be displayed here."
msgid "dashboard.empty-placeholder-files-title"
msgstr "No files yet."
-#: src/app/main/ui/dashboard/placeholder.cljs:39
+#: src/app/main/ui/dashboard/placeholder.cljs:40
#, markdown
msgid "dashboard.empty-placeholder-libraries"
msgstr ""
@@ -471,23 +472,23 @@ msgstr ""
"add from our [Libraries & "
"templates](https://penpot.app/libraries-templates)."
-#: src/app/main/ui/dashboard/placeholder.cljs:35
+#: src/app/main/ui/dashboard/placeholder.cljs:36
msgid "dashboard.empty-placeholder-libraries-subtitle-viewer-role"
msgstr "Libraries added to the project will appear here."
-#: src/app/main/ui/dashboard/placeholder.cljs:32
+#: src/app/main/ui/dashboard/placeholder.cljs:33
msgid "dashboard.empty-placeholder-libraries-title"
msgstr "No libraries yet."
-#: src/app/main/ui/dashboard/file_menu.cljs:259, src/app/main/ui/dashboard/file_menu.cljs:264
+#: src/app/main/ui/dashboard/file_menu.cljs:248, src/app/main/ui/dashboard/file_menu.cljs:253
msgid "dashboard.export-binary-multi"
msgstr "Download %s Penpot files (.penpot)"
-#: src/app/main/ui/workspace/main_menu.cljs:629
+#: src/app/main/ui/workspace/main_menu.cljs:678
msgid "dashboard.export-frames"
msgstr "Export boards as PDF"
-#: src/app/main/ui/exports/assets.cljs:206
+#: src/app/main/ui/exports/assets.cljs:199
msgid "dashboard.export-frames.title"
msgstr "Export as PDF"
@@ -495,33 +496,33 @@ msgstr "Export as PDF"
msgid "dashboard.export-multi"
msgstr "Export Penpot %s files"
-#: src/app/main/ui/exports/assets.cljs:113
+#: src/app/main/ui/exports/assets.cljs:106
msgid "dashboard.export-multiple.selected"
msgstr "%s of %s elements selected"
-#: src/app/main/ui/workspace/main_menu.cljs:591
+#: src/app/main/ui/workspace/main_menu.cljs:640
msgid "dashboard.export-shapes"
msgstr "Export"
-#: src/app/main/ui/exports/assets.cljs:184
+#: src/app/main/ui/exports/assets.cljs:177
msgid "dashboard.export-shapes.how-to"
msgstr ""
"You can add export settings to elements from the design properties (at the "
"bottom of the right sidebar)."
-#: src/app/main/ui/exports/assets.cljs:188
+#: src/app/main/ui/exports/assets.cljs:181
msgid "dashboard.export-shapes.how-to-link"
msgstr "Info how to set exports at Penpot."
-#: src/app/main/ui/exports/assets.cljs:183
+#: src/app/main/ui/exports/assets.cljs:176
msgid "dashboard.export-shapes.no-elements"
msgstr "There are no elements with export settings."
-#: src/app/main/ui/exports/assets.cljs:194
+#: src/app/main/ui/exports/assets.cljs:187
msgid "dashboard.export-shapes.title"
msgstr "Export selection"
-#: src/app/main/ui/dashboard/file_menu.cljs:269
+#: src/app/main/ui/dashboard/file_menu.cljs:258
msgid "dashboard.export-standard-multi"
msgstr "Download %s standard files (.svg + .json)"
@@ -633,7 +634,7 @@ msgstr ""
msgid "dashboard.import"
msgstr "Import Penpot files"
-#: src/app/main/ui/dashboard/import.cljs:288, src/app/worker/import.cljs:843, src/app/worker/import.cljs:846
+#: src/app/main/ui/dashboard/import.cljs:288, src/app/worker/import.cljs:851, src/app/worker/import.cljs:854
msgid "dashboard.import.analyze-error"
msgstr "Oops! We couldn't import this file"
@@ -687,7 +688,7 @@ msgstr "Uploading file: %s"
msgid "dashboard.invite-profile"
msgstr "Invite people"
-#: src/app/main/ui/dashboard/sidebar.cljs:547, src/app/main/ui/dashboard/sidebar.cljs:556, src/app/main/ui/dashboard/sidebar.cljs:563, src/app/main/ui/dashboard/team.cljs:341
+#: src/app/main/ui/dashboard/sidebar.cljs:547, src/app/main/ui/dashboard/sidebar.cljs:556, src/app/main/ui/dashboard/sidebar.cljs:563, src/app/main/ui/dashboard/team.cljs:344
msgid "dashboard.leave-team"
msgstr "Leave team"
@@ -699,7 +700,7 @@ msgstr "Libraries & Templates"
msgid "dashboard.libraries-and-templates.explore"
msgstr "Explore more of them and know how to contribute"
-#: src/app/main/ui/dashboard/import.cljs:358
+#: src/app/main/ui/dashboard/import.cljs:358, src/app/main/ui/workspace/libraries.cljs:123
msgid "dashboard.libraries-and-templates.import-error"
msgstr "There was a problem importing the template. The template wasn't imported."
@@ -707,7 +708,7 @@ msgstr "There was a problem importing the template. The template wasn't imported
msgid "dashboard.libraries-title"
msgstr "Libraries"
-#: src/app/main/ui/dashboard/placeholder.cljs:54
+#: src/app/main/ui/dashboard/placeholder.cljs:55
msgid "dashboard.loading-files"
msgstr "loading your files …"
@@ -715,15 +716,15 @@ msgstr "loading your files …"
msgid "dashboard.loading-fonts"
msgstr "loading your fonts …"
-#: src/app/main/ui/dashboard/file_menu.cljs:301, src/app/main/ui/dashboard/project_menu.cljs:100
+#: src/app/main/ui/dashboard/file_menu.cljs:290, src/app/main/ui/dashboard/project_menu.cljs:100
msgid "dashboard.move-to"
msgstr "Move to"
-#: src/app/main/ui/dashboard/file_menu.cljs:254
+#: src/app/main/ui/dashboard/file_menu.cljs:243
msgid "dashboard.move-to-multi"
msgstr "Move %s files to"
-#: src/app/main/ui/dashboard/file_menu.cljs:233
+#: src/app/main/ui/dashboard/file_menu.cljs:222
msgid "dashboard.move-to-other-team"
msgstr "Move to other team"
@@ -731,7 +732,7 @@ msgstr "Move to other team"
msgid "dashboard.new-file"
msgstr "+ New File"
-#: src/app/main/data/dashboard.cljs:966, src/app/main/data/dashboard.cljs:1189
+#: src/app/main/data/dashboard.cljs:968, src/app/main/data/dashboard.cljs:1195
msgid "dashboard.new-file-prefix"
msgstr "New File"
@@ -739,7 +740,7 @@ msgstr "New File"
msgid "dashboard.new-project"
msgstr "+ New project"
-#: src/app/main/data/dashboard.cljs:735, src/app/main/data/dashboard.cljs:1192
+#: src/app/main/data/dashboard.cljs:737, src/app/main/data/dashboard.cljs:1198
msgid "dashboard.new-project-prefix"
msgstr "New Project"
@@ -763,11 +764,11 @@ msgstr "Your email address has been verified successfully"
msgid "dashboard.notifications.password-saved"
msgstr "Password saved successfully!"
-#: src/app/main/ui/dashboard/team.cljs:1119
+#: src/app/main/ui/dashboard/team.cljs:1122
msgid "dashboard.num-of-members"
msgstr "%s members"
-#: src/app/main/ui/dashboard/file_menu.cljs:284
+#: src/app/main/ui/dashboard/file_menu.cljs:273
msgid "dashboard.open-in-new-tab"
msgstr "Open file in a new tab"
@@ -779,19 +780,19 @@ msgstr "Options"
msgid "dashboard.password-change"
msgstr "Change password"
-#: src/app/main/data/common.cljs:205
+#: src/app/main/data/common.cljs:202
msgid "dashboard.permissions-change.admin"
msgstr "You are now an admin on this team."
-#: src/app/main/data/common.cljs:204
+#: src/app/main/data/common.cljs:201
msgid "dashboard.permissions-change.editor"
msgstr "You are now an editor on this team."
-#: src/app/main/data/common.cljs:206
+#: src/app/main/data/common.cljs:203
msgid "dashboard.permissions-change.owner"
msgstr "You are now owner on this team."
-#: src/app/main/data/common.cljs:203
+#: src/app/main/data/common.cljs:200
msgid "dashboard.permissions-change.viewer"
msgstr "You are now a viewer on this team."
@@ -812,7 +813,7 @@ msgstr "Want to remove your account?"
msgid "dashboard.remove-shared"
msgstr "Remove as Shared Library"
-#: src/app/main/data/common.cljs:233
+#: src/app/main/data/common.cljs:235
msgid "dashboard.removed-from-team"
msgstr "You are not part of the team “%s“ anymore."
@@ -840,7 +841,11 @@ msgstr "Select theme"
msgid "dashboard.show-all-files"
msgstr "Show all files"
-#: src/app/main/ui/dashboard/file_menu.cljs:101
+#: src/app/main/ui/workspace/main_menu.cljs:632
+msgid "dashboard.show-version-history"
+msgstr "Version history"
+
+#: src/app/main/ui/dashboard/file_menu.cljs:98
msgid "dashboard.success-delete-file"
msgid_plural "dashboard.success-delete-file"
msgstr[0] "Your file has been deleted successfully"
@@ -850,7 +855,7 @@ msgstr[1] "Your files have been deleted successfully"
msgid "dashboard.success-delete-project"
msgstr "Your project has been deleted successfully"
-#: src/app/main/ui/dashboard/file_menu.cljs:96
+#: src/app/main/ui/dashboard/file_menu.cljs:93
msgid "dashboard.success-duplicate-file"
msgid_plural "dashboard.success-delete-file"
msgstr[0] "Your file has been duplicated successfully"
@@ -860,11 +865,11 @@ msgstr[1] "Your files have been duplicated successfully"
msgid "dashboard.success-duplicate-project"
msgstr "Your project has been duplicated successfully"
-#: src/app/main/ui/dashboard/file_menu.cljs:135, src/app/main/ui/dashboard/grid.cljs:575, src/app/main/ui/dashboard/sidebar.cljs:152
+#: src/app/main/ui/dashboard/file_menu.cljs:132, src/app/main/ui/dashboard/grid.cljs:574, src/app/main/ui/dashboard/sidebar.cljs:152
msgid "dashboard.success-move-file"
msgstr "Your file has been moved successfully"
-#: src/app/main/ui/dashboard/file_menu.cljs:134
+#: src/app/main/ui/dashboard/file_menu.cljs:131
msgid "dashboard.success-move-files"
msgstr "Your files have been moved successfully"
@@ -872,15 +877,15 @@ msgstr "Your files have been moved successfully"
msgid "dashboard.success-move-project"
msgstr "Your project has been moved successfully"
-#: src/app/main/ui/dashboard/team.cljs:1090
+#: src/app/main/ui/dashboard/team.cljs:1093
msgid "dashboard.team-info"
msgstr "Team info"
-#: src/app/main/ui/dashboard/team.cljs:1108
+#: src/app/main/ui/dashboard/team.cljs:1111
msgid "dashboard.team-members"
msgstr "Team members"
-#: src/app/main/ui/dashboard/team.cljs:1123
+#: src/app/main/ui/dashboard/team.cljs:1126
msgid "dashboard.team-projects"
msgstr "Team projects"
@@ -896,7 +901,7 @@ msgstr "Search results"
msgid "dashboard.type-something"
msgstr "Type to search results"
-#: src/app/main/ui/dashboard/file_menu.cljs:308, src/app/main/ui/workspace/main_menu.cljs:578
+#: src/app/main/ui/dashboard/file_menu.cljs:297, src/app/main/ui/workspace/main_menu.cljs:606
msgid "dashboard.unpublish-shared"
msgstr "Unpublish Library"
@@ -904,42 +909,42 @@ msgstr "Unpublish Library"
msgid "dashboard.update-settings"
msgstr "Update settings"
-#: src/app/main/ui/dashboard/team.cljs:899
+#: src/app/main/ui/dashboard/team.cljs:902
msgid "dashboard.webhooks.active"
msgstr "Is active"
-#: src/app/main/ui/dashboard/team.cljs:900
+#: src/app/main/ui/dashboard/team.cljs:903
msgid "dashboard.webhooks.active.explain"
msgstr "When this hook is triggered event details will be delivered"
-#: src/app/main/ui/dashboard/team.cljs:944
+#: src/app/main/ui/dashboard/team.cljs:947
msgid "dashboard.webhooks.cant-edit"
msgstr "You only can delete or modify webhooks created by you."
-#: src/app/main/ui/dashboard/team.cljs:890
+#: src/app/main/ui/dashboard/team.cljs:893
msgid "dashboard.webhooks.content-type"
msgstr "Content type"
-#: src/app/main/ui/dashboard/team.cljs:923
+#: src/app/main/ui/dashboard/team.cljs:926
msgid "dashboard.webhooks.create"
msgstr "Create webhook"
-#: src/app/main/ui/dashboard/team.cljs:813
+#: src/app/main/ui/dashboard/team.cljs:816
msgid "dashboard.webhooks.create.success"
msgstr "Webhook created successfully."
-#: src/app/main/ui/dashboard/team.cljs:920
+#: src/app/main/ui/dashboard/team.cljs:923
msgid "dashboard.webhooks.description"
msgstr ""
"Webhooks are a simple way to allow other websites and apps to be notified "
"when certain events happen at Penpot. We’ll send a POST request to each of "
"the URLs you provide."
-#: src/app/main/ui/dashboard/team.cljs:1043
+#: src/app/main/ui/dashboard/team.cljs:1046
msgid "dashboard.webhooks.empty.add-one"
msgstr "Press the button \"Add webhook\" to add one."
-#: src/app/main/ui/dashboard/team.cljs:1042
+#: src/app/main/ui/dashboard/team.cljs:1045
msgid "dashboard.webhooks.empty.no-webhooks"
msgstr "No webhooks created so far."
@@ -959,7 +964,7 @@ msgstr "Email"
msgid "dashboard.your-name"
msgstr "Your name"
-#: src/app/main/ui/dashboard/file_menu.cljs:40, src/app/main/ui/dashboard/fonts.cljs:34, src/app/main/ui/dashboard/libraries.cljs:44, src/app/main/ui/dashboard/projects.cljs:341, src/app/main/ui/dashboard/search.cljs:31, src/app/main/ui/dashboard/sidebar.cljs:312, src/app/main/ui/dashboard/team.cljs:526, src/app/main/ui/dashboard/team.cljs:766, src/app/main/ui/dashboard/team.cljs:1029, src/app/main/ui/dashboard/team.cljs:1076
+#: src/app/main/ui/dashboard/file_menu.cljs:39, src/app/main/ui/dashboard/fonts.cljs:34, src/app/main/ui/dashboard/libraries.cljs:44, src/app/main/ui/dashboard/projects.cljs:341, src/app/main/ui/dashboard/search.cljs:31, src/app/main/ui/dashboard/sidebar.cljs:312, src/app/main/ui/dashboard/team.cljs:529, src/app/main/ui/dashboard/team.cljs:769, src/app/main/ui/dashboard/team.cljs:1032, src/app/main/ui/dashboard/team.cljs:1079
msgid "dashboard.your-penpot"
msgstr "Your Penpot"
@@ -991,7 +996,7 @@ msgstr "Ok"
msgid "ds.confirm-title"
msgstr "Are you sure?"
-#: src/app/main/data/users.cljs:733
+#: src/app/main/data/users.cljs:734
msgid "errors.auth-provider-not-allowed"
msgstr "Auth provider not allowed for this profile"
@@ -1015,7 +1020,7 @@ msgstr "The fonts %s could not be loaded"
msgid "errors.cannot-upload"
msgstr "Cannot upload the media file."
-#: src/app/main/data/workspace.cljs:1672
+#: src/app/main/data/workspace.cljs:1678
msgid "errors.clipboard-not-implemented"
msgstr "Your browser cannot do this operation"
@@ -1031,11 +1036,11 @@ msgstr "Email already validated."
msgid "errors.email-as-password"
msgstr "You can't use your email as password"
-#: src/app/main/data/users.cljs:735, src/app/main/ui/auth/register.cljs:54
+#: src/app/main/data/users.cljs:736, src/app/main/ui/auth/register.cljs:54
msgid "errors.email-domain-not-allowed"
msgstr "Domain not allowed"
-#: src/app/main/ui/auth/recovery_request.cljs:57, src/app/main/ui/auth/register.cljs:57, src/app/main/ui/auth/register.cljs:60, src/app/main/ui/dashboard/team.cljs:615, src/app/main/ui/settings/change_email.cljs:37
+#: src/app/main/ui/auth/recovery_request.cljs:57, src/app/main/ui/auth/register.cljs:57, src/app/main/ui/auth/register.cljs:60, src/app/main/ui/dashboard/team.cljs:618, src/app/main/ui/settings/change_email.cljs:37
msgid "errors.email-has-permanent-bounces"
msgstr "The email «%s» has many permanent bounce reports."
@@ -1074,7 +1079,7 @@ msgstr ""
"features of the file you are trying to open. Migrations for '%s' need to be "
"applied before the file can be opened."
-#: src/app/main/data/users.cljs:741, src/app/main/ui/auth/login.cljs:80, src/app/main/ui/auth/login.cljs:121, src/app/main/ui/auth/register.cljs:66, src/app/main/ui/auth/register.cljs:207, src/app/main/ui/auth/verify_token.cljs:91, src/app/main/ui/dashboard/team.cljs:193, src/app/main/ui/onboarding/team_choice.cljs:112, src/app/main/ui/settings/access_tokens.cljs:80, src/app/main/ui/settings/feedback.cljs:49
+#: src/app/main/data/users.cljs:742, src/app/main/ui/auth/login.cljs:80, src/app/main/ui/auth/login.cljs:121, src/app/main/ui/auth/register.cljs:66, src/app/main/ui/auth/register.cljs:207, src/app/main/ui/auth/verify_token.cljs:91, src/app/main/ui/dashboard/team.cljs:193, src/app/main/ui/onboarding/team_choice.cljs:112, src/app/main/ui/settings/access_tokens.cljs:80, src/app/main/ui/settings/feedback.cljs:49
msgid "errors.generic"
msgstr "Something wrong has happened."
@@ -1130,7 +1135,7 @@ msgstr "Seems that the contents of the image does not match the file extension."
msgid "errors.media-type-not-allowed"
msgstr "Seems that this is not a valid image."
-#: src/app/main/ui/dashboard/team.cljs:610
+#: src/app/main/ui/dashboard/team.cljs:613
msgid "errors.member-is-muted"
msgstr "The profile you inviting has emails muted (spam reports or high bounces)."
@@ -1153,15 +1158,15 @@ msgstr "Password should at least be 8 characters"
msgid "errors.paste-data-validation"
msgstr "Invalid data in clipboard"
-#: src/app/main/data/users.cljs:731, src/app/main/ui/auth/login.cljs:102, src/app/main/ui/auth/login.cljs:110
+#: src/app/main/data/users.cljs:732, src/app/main/ui/auth/login.cljs:102, src/app/main/ui/auth/login.cljs:110
msgid "errors.profile-blocked"
msgstr "The profile is blocked"
-#: src/app/main/ui/auth/recovery_request.cljs:53, src/app/main/ui/dashboard/team.cljs:176, src/app/main/ui/dashboard/team.cljs:606, src/app/main/ui/onboarding/team_choice.cljs:96, src/app/main/ui/settings/change_email.cljs:33
+#: src/app/main/ui/auth/recovery_request.cljs:53, src/app/main/ui/dashboard/team.cljs:176, src/app/main/ui/dashboard/team.cljs:609, src/app/main/ui/onboarding/team_choice.cljs:96, src/app/main/ui/settings/change_email.cljs:33
msgid "errors.profile-is-muted"
msgstr "Your profile has emails muted (spam reports or high bounces)."
-#: src/app/main/data/users.cljs:729, src/app/main/ui/auth/register.cljs:51
+#: src/app/main/data/users.cljs:730, src/app/main/ui/auth/register.cljs:51
msgid "errors.registration-disabled"
msgstr "The registration is currently disabled."
@@ -1169,15 +1174,15 @@ msgstr "The registration is currently disabled."
msgid "errors.team-feature-mismatch"
msgstr "Detected incompatible feature '%s'"
-#: src/app/main/ui/dashboard/sidebar.cljs:365, src/app/main/ui/dashboard/team.cljs:389
+#: src/app/main/ui/dashboard/sidebar.cljs:365, src/app/main/ui/dashboard/team.cljs:392
msgid "errors.team-leave.insufficient-members"
msgstr "Insufficient members to leave team, you probably want to delete it."
-#: src/app/main/ui/dashboard/sidebar.cljs:368, src/app/main/ui/dashboard/team.cljs:392
+#: src/app/main/ui/dashboard/sidebar.cljs:368, src/app/main/ui/dashboard/team.cljs:395
msgid "errors.team-leave.member-does-not-exists"
msgstr "The member you try to assign does not exist."
-#: src/app/main/ui/dashboard/sidebar.cljs:371, src/app/main/ui/dashboard/team.cljs:395
+#: src/app/main/ui/dashboard/sidebar.cljs:371, src/app/main/ui/dashboard/team.cljs:398
msgid "errors.team-leave.owner-cant-leave"
msgstr "Owner can't leave team, you must reassign the owner role."
@@ -1197,31 +1202,31 @@ msgstr "Validation Error"
msgid "errors.version-not-supported"
msgstr "File has an incompatible version number"
-#: src/app/main/ui/dashboard/team.cljs:834
+#: src/app/main/ui/dashboard/team.cljs:837
msgid "errors.webhooks.connection"
msgstr "Connection error, URL not reacheable"
-#: src/app/main/ui/dashboard/team.cljs:828
+#: src/app/main/ui/dashboard/team.cljs:831
msgid "errors.webhooks.invalid-uri"
msgstr "URL does not pass validation."
-#: src/app/main/ui/dashboard/team.cljs:986
+#: src/app/main/ui/dashboard/team.cljs:989
msgid "errors.webhooks.last-delivery"
msgstr "Last delivery was not successful."
-#: src/app/main/ui/dashboard/team.cljs:830, src/app/main/ui/dashboard/team.cljs:989
+#: src/app/main/ui/dashboard/team.cljs:833, src/app/main/ui/dashboard/team.cljs:992
msgid "errors.webhooks.ssl-validation"
msgstr "Error on SSL validation."
-#: src/app/main/ui/dashboard/team.cljs:832
+#: src/app/main/ui/dashboard/team.cljs:835
msgid "errors.webhooks.timeout"
msgstr "Timeout"
-#: src/app/main/ui/dashboard/team.cljs:826
+#: src/app/main/ui/dashboard/team.cljs:829
msgid "errors.webhooks.unexpected"
msgstr "Unexpected error on validating"
-#: src/app/main/ui/dashboard/team.cljs:836, src/app/main/ui/dashboard/team.cljs:992
+#: src/app/main/ui/dashboard/team.cljs:839, src/app/main/ui/dashboard/team.cljs:995
msgid "errors.webhooks.unexpected-status"
msgstr "Unexpected status %s"
@@ -1471,71 +1476,71 @@ msgstr "Unset"
msgid "inspect.attributes.typography.text-transform.uppercase"
msgstr "Upper Case"
-#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:152
+#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:157
msgid "inspect.empty.help"
msgstr "If you want to know more about design inspect visit Penpot's help center"
-#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:155
+#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:160
msgid "inspect.empty.more-info"
msgstr "More info about inspect"
-#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:147
+#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:152
msgid "inspect.empty.select"
msgstr "Select a shape, board or group to inspect their properties and code"
-#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:100
+#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:105
msgid "inspect.tabs.code"
msgstr "Code"
-#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:124
+#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:129
msgid "inspect.tabs.code.selected.circle"
msgstr "Circle"
-#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:125
+#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:130
msgid "inspect.tabs.code.selected.component"
msgstr "Component"
-#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:126
+#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:131
msgid "inspect.tabs.code.selected.curve"
msgstr "Curve"
-#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:127
+#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:132
msgid "inspect.tabs.code.selected.frame"
msgstr "Board"
-#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:128
+#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:133
msgid "inspect.tabs.code.selected.group"
msgstr "Group"
-#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:129
+#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:134
msgid "inspect.tabs.code.selected.image"
msgstr "Image"
-#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:130
+#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:135
msgid "inspect.tabs.code.selected.mask"
msgstr "Mask"
-#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:119
+#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:124
msgid "inspect.tabs.code.selected.multiple"
msgstr "%s Selected"
-#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:131
+#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:136
msgid "inspect.tabs.code.selected.path"
msgstr "Path"
-#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:132
+#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:137
msgid "inspect.tabs.code.selected.rect"
msgstr "Rectangle"
-#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:133
+#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:138
msgid "inspect.tabs.code.selected.svg-raw"
msgstr "SVG"
-#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:134
+#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:139
msgid "inspect.tabs.code.selected.text"
msgstr "Text"
-#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:96
+#: src/app/main/ui/viewer/inspect/right_sidebar.cljs:101
msgid "inspect.tabs.info"
msgstr "Info"
@@ -1544,11 +1549,11 @@ msgstr "Info"
msgid "intersection"
msgstr ""
-#: src/app/main/ui/workspace/main_menu.cljs:162
+#: src/app/main/ui/workspace/main_menu.cljs:163
msgid "label.shortcuts"
msgstr "Shortcuts"
-#: src/app/main/data/common.cljs:93, src/app/main/ui/dashboard/import.cljs:503
+#: src/app/main/data/common.cljs:90, src/app/main/ui/dashboard/import.cljs:503
msgid "labels.accept"
msgstr "Accept"
@@ -1556,23 +1561,23 @@ msgstr "Accept"
msgid "labels.access-tokens"
msgstr "Access tokens"
-#: src/app/main/ui/dashboard/team.cljs:1005
+#: src/app/main/ui/dashboard/team.cljs:1008
msgid "labels.active"
msgstr "Active"
-#: src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs:1051
-#, unused
+#: src/app/main/ui/workspace/libraries.cljs:148
msgid "labels.add"
msgstr "Add"
-msgid "labels.adding"
-msgstr "Adding..."
-
#: src/app/main/ui/dashboard/fonts.cljs:176
msgid "labels.add-custom-font"
msgstr "Add custom font"
-#: src/app/main/ui/dashboard/team.cljs:128, src/app/main/ui/dashboard/team.cljs:310, src/app/main/ui/dashboard/team.cljs:550, src/app/main/ui/dashboard/team.cljs:580, src/app/main/ui/onboarding/team_choice.cljs:66
+#: src/app/main/ui/workspace/libraries.cljs:148
+msgid "labels.adding"
+msgstr "Adding..."
+
+#: src/app/main/ui/dashboard/team.cljs:128, src/app/main/ui/dashboard/team.cljs:313, src/app/main/ui/dashboard/team.cljs:553, src/app/main/ui/dashboard/team.cljs:583, src/app/main/ui/onboarding/team_choice.cljs:66
msgid "labels.admin"
msgstr "Admin"
@@ -1603,7 +1608,7 @@ msgstr ""
msgid "labels.bad-gateway.main-message"
msgstr "Bad Gateway"
-#: src/app/main/data/common.cljs:131, src/app/main/ui/dashboard/change_owner.cljs:68, src/app/main/ui/dashboard/import.cljs:489, src/app/main/ui/dashboard/team.cljs:906, src/app/main/ui/delete_shared.cljs:35, src/app/main/ui/exports/assets.cljs:168, src/app/main/ui/exports/files.cljs:192, src/app/main/ui/settings/access_tokens.cljs:177, src/app/main/ui/viewer/share_link.cljs:205, src/app/main/ui/workspace/sidebar/assets/groups.cljs:145, src/app/main/ui/workspace/tokens/form.cljs:429, src/app/main/ui/workspace/tokens/modals/themes.cljs:203
+#: src/app/main/data/common.cljs:128, src/app/main/ui/dashboard/change_owner.cljs:68, src/app/main/ui/dashboard/import.cljs:489, src/app/main/ui/dashboard/team.cljs:909, src/app/main/ui/delete_shared.cljs:35, src/app/main/ui/exports/assets.cljs:161, src/app/main/ui/exports/files.cljs:192, src/app/main/ui/settings/access_tokens.cljs:177, src/app/main/ui/viewer/share_link.cljs:205, src/app/main/ui/workspace/sidebar/assets/groups.cljs:145, src/app/main/ui/workspace/tokens/form.cljs:429, src/app/main/ui/workspace/tokens/modals/themes.cljs:203
msgid "labels.cancel"
msgstr "Cancel"
@@ -1611,7 +1616,7 @@ msgstr "Cancel"
msgid "labels.canva"
msgstr "Canva"
-#: src/app/main/ui/dashboard/projects.cljs:96, src/app/main/ui/exports/files.cljs:210, src/app/main/ui/settings/access_tokens.cljs:172, src/app/main/ui/viewer/login.cljs:71, src/app/main/ui/viewer/share_link.cljs:176, src/app/main/ui/workspace/comments.cljs:129, src/app/main/ui/workspace/libraries.cljs:538, src/app/main/ui/workspace/sidebar/debug.cljs:40, src/app/main/ui/workspace/sidebar/layers.cljs:299, src/app/main/ui/workspace/tokens/modals/themes.cljs:366, src/app/main/ui/workspace/tokens/modals.cljs:56
+#: src/app/main/ui/dashboard/projects.cljs:96, src/app/main/ui/exports/files.cljs:210, src/app/main/ui/settings/access_tokens.cljs:172, src/app/main/ui/viewer/login.cljs:71, src/app/main/ui/viewer/share_link.cljs:176, src/app/main/ui/workspace/comments.cljs:129, src/app/main/ui/workspace/libraries.cljs:607, src/app/main/ui/workspace/sidebar/debug.cljs:40, src/app/main/ui/workspace/sidebar/layers.cljs:300, src/app/main/ui/workspace/tokens/modals/themes.cljs:366, src/app/main/ui/workspace/tokens/modals.cljs:56
msgid "labels.close"
msgstr "Close"
@@ -1623,7 +1628,7 @@ msgstr "Collapse"
msgid "labels.comments"
msgstr "Comments"
-#: src/app/main/ui/dashboard/sidebar.cljs:985, src/app/main/ui/workspace/main_menu.cljs:114
+#: src/app/main/ui/dashboard/sidebar.cljs:985, src/app/main/ui/workspace/main_menu.cljs:115
msgid "labels.community"
msgstr "Community"
@@ -1643,7 +1648,7 @@ msgstr "Continue with"
msgid "labels.continue-with-penpot"
msgstr "You can continue with a Penpot account"
-#: src/app/main/ui/dashboard/team.cljs:678
+#: src/app/main/ui/dashboard/team.cljs:681
msgid "labels.copy-invitation-link"
msgstr "Copy link"
@@ -1655,11 +1660,11 @@ msgstr "Kaleidos @2024"
msgid "labels.create"
msgstr "Create"
-#: src/app/main/ui/dashboard/team_form.cljs:101, src/app/main/ui/dashboard/team_form.cljs:121
+#: src/app/main/ui/dashboard/team_form.cljs:103, src/app/main/ui/dashboard/team_form.cljs:123
msgid "labels.create-team"
msgstr "Create new team"
-#: src/app/main/ui/dashboard/team_form.cljs:113
+#: src/app/main/ui/dashboard/team_form.cljs:115
msgid "labels.create-team.placeholder"
msgstr "Enter new team name"
@@ -1671,7 +1676,7 @@ msgstr "Custom fonts"
msgid "labels.dashboard"
msgstr "Dashboard"
-#: src/app/main/ui/dashboard/file_menu.cljs:336, src/app/main/ui/dashboard/fonts.cljs:256, src/app/main/ui/dashboard/fonts.cljs:332, src/app/main/ui/dashboard/fonts.cljs:346, src/app/main/ui/dashboard/project_menu.cljs:114, src/app/main/ui/dashboard/team.cljs:942, src/app/main/ui/settings/access_tokens.cljs:198, src/app/main/ui/workspace/sidebar/options/menus/component.cljs:209, src/app/main/ui/workspace/sidebar/versions.cljs:152, src/app/main/ui/workspace/tokens/form.cljs:425, src/app/main/ui/workspace/tokens/modals/themes.cljs:335, src/app/main/ui/workspace/tokens/sets_context_menu.cljs:44
+#: src/app/main/ui/dashboard/file_menu.cljs:325, src/app/main/ui/dashboard/fonts.cljs:256, src/app/main/ui/dashboard/fonts.cljs:332, src/app/main/ui/dashboard/fonts.cljs:346, src/app/main/ui/dashboard/project_menu.cljs:114, src/app/main/ui/dashboard/team.cljs:945, src/app/main/ui/settings/access_tokens.cljs:198, src/app/main/ui/workspace/sidebar/options/menus/component.cljs:209, src/app/main/ui/workspace/sidebar/versions.cljs:155, src/app/main/ui/workspace/tokens/form.cljs:425, src/app/main/ui/workspace/tokens/modals/themes.cljs:335, src/app/main/ui/workspace/tokens/sets_context_menu.cljs:44
msgid "labels.delete"
msgstr "Delete"
@@ -1683,11 +1688,11 @@ msgstr "Delete comment"
msgid "labels.delete-comment-thread"
msgstr "Delete thread"
-#: src/app/main/ui/dashboard/team.cljs:684
+#: src/app/main/ui/dashboard/team.cljs:687
msgid "labels.delete-invitation"
msgstr "Delete invitation"
-#: src/app/main/ui/dashboard/file_menu.cljs:280
+#: src/app/main/ui/dashboard/file_menu.cljs:269
msgid "labels.delete-multi-files"
msgstr "Delete %s files"
@@ -1703,11 +1708,11 @@ msgstr "Director"
msgid "labels.discard"
msgstr "Discard"
-#: src/app/main/ui/dashboard/file_menu.cljs:30, src/app/main/ui/dashboard/files.cljs:75, src/app/main/ui/dashboard/files.cljs:168, src/app/main/ui/dashboard/projects.cljs:225, src/app/main/ui/dashboard/projects.cljs:229, src/app/main/ui/dashboard/sidebar.cljs:791
+#: src/app/main/ui/dashboard/file_menu.cljs:29, src/app/main/ui/dashboard/files.cljs:75, src/app/main/ui/dashboard/files.cljs:168, src/app/main/ui/dashboard/projects.cljs:225, src/app/main/ui/dashboard/projects.cljs:229, src/app/main/ui/dashboard/sidebar.cljs:791
msgid "labels.drafts"
msgstr "Drafts"
-#: src/app/main/ui/comments.cljs:356, src/app/main/ui/dashboard/fonts.cljs:253, src/app/main/ui/dashboard/team.cljs:940, src/app/main/ui/workspace/sidebar/options/menus/component.cljs:205, src/app/main/ui/workspace/tokens/sidebar.cljs:199
+#: src/app/main/ui/comments.cljs:356, src/app/main/ui/dashboard/fonts.cljs:253, src/app/main/ui/dashboard/team.cljs:943, src/app/main/ui/workspace/sidebar/options/menus/component.cljs:205, src/app/main/ui/workspace/tokens/sidebar.cljs:199
msgid "labels.edit"
msgstr "Edit"
@@ -1715,7 +1720,7 @@ msgstr "Edit"
msgid "labels.edit-file"
msgstr "Edit file"
-#: src/app/main/ui/dashboard/team.cljs:126, src/app/main/ui/dashboard/team.cljs:307, src/app/main/ui/dashboard/team.cljs:551, src/app/main/ui/dashboard/team.cljs:584, src/app/main/ui/onboarding/team_choice.cljs:65
+#: src/app/main/ui/dashboard/team.cljs:126, src/app/main/ui/dashboard/team.cljs:310, src/app/main/ui/dashboard/team.cljs:554, src/app/main/ui/dashboard/team.cljs:587, src/app/main/ui/onboarding/team_choice.cljs:65
msgid "labels.editor"
msgstr "Editor"
@@ -1723,11 +1728,11 @@ msgstr "Editor"
msgid "labels.event"
msgstr "Event"
-#: src/app/main/ui/dashboard/team.cljs:698
+#: src/app/main/ui/dashboard/team.cljs:701
msgid "labels.expired-invitation"
msgstr "Expired"
-#: src/app/main/ui/exports/assets.cljs:177
+#: src/app/main/ui/exports/assets.cljs:170
msgid "labels.export"
msgstr "Export"
@@ -1767,11 +1772,11 @@ msgstr "CEO or Founder"
msgid "labels.freelancer"
msgstr "Freelancer"
-#: src/app/main/ui/dashboard/sidebar.cljs:1015, src/app/main/ui/workspace/main_menu.cljs:146
+#: src/app/main/ui/dashboard/sidebar.cljs:1015, src/app/main/ui/workspace/main_menu.cljs:147
msgid "labels.github-repo"
msgstr "Github repository"
-#: src/app/main/ui/dashboard/sidebar.cljs:1032, src/app/main/ui/settings/sidebar.cljs:113, src/app/main/ui/workspace/main_menu.cljs:175
+#: src/app/main/ui/dashboard/sidebar.cljs:1032, src/app/main/ui/settings/sidebar.cljs:113, src/app/main/ui/workspace/main_menu.cljs:176
msgid "labels.give-feedback"
msgstr "Give feedback"
@@ -1783,7 +1788,7 @@ msgstr "Go back"
msgid "labels.graphic-design"
msgstr "Graphic design"
-#: src/app/main/ui/dashboard/sidebar.cljs:978, src/app/main/ui/workspace/main_menu.cljs:106, src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs:1079, src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs:1104, src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs:1290
+#: src/app/main/ui/dashboard/sidebar.cljs:978, src/app/main/ui/workspace/main_menu.cljs:107, src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs:1079, src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs:1104, src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs:1290
msgid "labels.help-center"
msgstr "Help Center"
@@ -1791,7 +1796,7 @@ msgstr "Help Center"
msgid "labels.hide-resolved-comments"
msgstr "Hide resolved comments"
-#: src/app/main/ui/dashboard/team.cljs:1006
+#: src/app/main/ui/dashboard/team.cljs:1009
msgid "labels.inactive"
msgstr "Inactive"
@@ -1813,7 +1818,7 @@ msgstr "Internal Error"
msgid "labels.invision"
msgstr "InVision"
-#: src/app/main/ui/dashboard/sidebar.cljs:516, src/app/main/ui/dashboard/team.cljs:96, src/app/main/ui/dashboard/team.cljs:104, src/app/main/ui/dashboard/team.cljs:745
+#: src/app/main/ui/dashboard/sidebar.cljs:516, src/app/main/ui/dashboard/team.cljs:96, src/app/main/ui/dashboard/team.cljs:104, src/app/main/ui/dashboard/team.cljs:748
msgid "labels.invitations"
msgstr "Invitations"
@@ -1821,11 +1826,11 @@ msgstr "Invitations"
msgid "labels.language"
msgstr "Language"
-#: src/app/main/ui/dashboard/sidebar.cljs:1008, src/app/main/ui/workspace/main_menu.cljs:138
+#: src/app/main/ui/dashboard/sidebar.cljs:1008, src/app/main/ui/workspace/main_menu.cljs:139
msgid "labels.libraries-and-templates"
msgstr "Libraries & Templates"
-#: src/app/main/ui/auth/verify_token.cljs:97, src/app/main/ui/dashboard/grid.cljs:104, src/app/main/ui/dashboard/grid.cljs:124, src/app/main/ui/dashboard/import.cljs:253, src/app/main/ui/dashboard/placeholder.cljs:52, src/app/main/ui/ds/product/loader.cljs:52, src/app/main/ui/exports/files.cljs:62, src/app/main/ui/viewer.cljs:637, src/app/main/ui/workspace.cljs:129
+#: src/app/main/ui/auth/verify_token.cljs:97, src/app/main/ui/dashboard/grid.cljs:104, src/app/main/ui/dashboard/grid.cljs:124, src/app/main/ui/dashboard/import.cljs:253, src/app/main/ui/dashboard/placeholder.cljs:53, src/app/main/ui/ds/product/loader.cljs:52, src/app/main/ui/exports/files.cljs:62, src/app/main/ui/viewer.cljs:637, src/app/main/ui/workspace.cljs:129
msgid "labels.loading"
msgstr "Loading…"
@@ -1845,7 +1850,7 @@ msgstr "Logout"
msgid "labels.marketing"
msgstr "Marketing"
-#: src/app/main/ui/dashboard/team.cljs:499
+#: src/app/main/ui/dashboard/team.cljs:502
msgid "labels.member"
msgstr "Member"
@@ -1865,11 +1870,11 @@ msgstr "Next"
msgid "labels.no-comments-available"
msgstr "You're all caught up! New comment notifications will appear here."
-#: src/app/main/ui/dashboard/team.cljs:731
+#: src/app/main/ui/dashboard/team.cljs:734
msgid "labels.no-invitations"
msgstr "No pending invitations."
-#: src/app/main/ui/dashboard/team.cljs:733
+#: src/app/main/ui/dashboard/team.cljs:736
#, markdown
msgid "labels.no-invitations-hint"
msgstr "Click the **Invite people** button to invite people to this team."
@@ -1883,7 +1888,7 @@ msgstr "This page might not exist or you don’t have permissions to access to i
msgid "labels.not-found.main-message"
msgstr "Oops!"
-#: src/app/main/ui/dashboard/projects.cljs:237, src/app/main/ui/dashboard/team.cljs:1133
+#: src/app/main/ui/dashboard/projects.cljs:237, src/app/main/ui/dashboard/team.cljs:1136
msgid "labels.num-of-files"
msgid_plural "labels.num-of-files"
msgstr[0] "1 file"
@@ -1895,7 +1900,7 @@ msgid_plural "labels.num-of-frames"
msgstr[0] "1 board"
msgstr[1] "%s boards"
-#: src/app/main/ui/dashboard/team.cljs:1128
+#: src/app/main/ui/dashboard/team.cljs:1131
msgid "labels.num-of-projects"
msgid_plural "labels.num-of-projects"
msgstr[0] "1 project"
@@ -1930,7 +1935,7 @@ msgstr "Other (specify)"
msgid "labels.other-short"
msgstr "Other"
-#: src/app/main/ui/dashboard/team.cljs:314, src/app/main/ui/dashboard/team.cljs:549, src/app/main/ui/dashboard/team.cljs:1114
+#: src/app/main/ui/dashboard/team.cljs:317, src/app/main/ui/dashboard/team.cljs:552, src/app/main/ui/dashboard/team.cljs:1117
msgid "labels.owner"
msgstr "Owner"
@@ -1938,7 +1943,7 @@ msgstr "Owner"
msgid "labels.password"
msgstr "Password"
-#: src/app/main/ui/dashboard/team.cljs:699
+#: src/app/main/ui/dashboard/team.cljs:702
msgid "labels.pending-invitation"
msgstr "Pending"
@@ -1962,7 +1967,7 @@ msgstr "Profile"
msgid "labels.projects"
msgstr "Projects"
-#: src/app/main/ui/dashboard/sidebar.cljs:998, src/app/main/ui/settings/sidebar.cljs:106, src/app/main/ui/workspace/main_menu.cljs:130
+#: src/app/main/ui/dashboard/sidebar.cljs:998, src/app/main/ui/settings/sidebar.cljs:106, src/app/main/ui/workspace/main_menu.cljs:131
msgid "labels.release-notes"
msgstr "Release notes"
@@ -1976,23 +1981,23 @@ msgstr "Reload file"
msgid "labels.remove"
msgstr "Remove"
-#: src/app/main/ui/dashboard/team.cljs:345
+#: src/app/main/ui/dashboard/team.cljs:348
msgid "labels.remove-member"
msgstr "Remove member"
-#: src/app/main/ui/dashboard/file_menu.cljs:288, src/app/main/ui/dashboard/project_menu.cljs:87, src/app/main/ui/dashboard/sidebar.cljs:539, src/app/main/ui/workspace/sidebar/assets/groups.cljs:153, src/app/main/ui/workspace/sidebar/versions.cljs:146, src/app/main/ui/workspace/tokens/sets_context_menu.cljs:43
+#: src/app/main/ui/dashboard/file_menu.cljs:277, src/app/main/ui/dashboard/project_menu.cljs:87, src/app/main/ui/dashboard/sidebar.cljs:539, src/app/main/ui/workspace/sidebar/assets/groups.cljs:153, src/app/main/ui/workspace/sidebar/versions.cljs:149, src/app/main/ui/workspace/tokens/sets_context_menu.cljs:43
msgid "labels.rename"
msgstr "Rename"
-#: src/app/main/ui/dashboard/team_form.cljs:99
+#: src/app/main/ui/dashboard/team_form.cljs:101
msgid "labels.rename-team"
msgstr "Rename team"
-#: src/app/main/ui/dashboard/team.cljs:681
+#: src/app/main/ui/dashboard/team.cljs:684
msgid "labels.resend-invitation"
msgstr "Resend invitation"
-#: src/app/main/ui/workspace/sidebar/versions.cljs:149, src/app/main/ui/workspace/sidebar/versions.cljs:288
+#: src/app/main/ui/workspace/sidebar/versions.cljs:152, src/app/main/ui/workspace/sidebar/versions.cljs:292
msgid "labels.restore"
msgstr "Restore"
@@ -2000,7 +2005,7 @@ msgstr "Restore"
msgid "labels.retry"
msgstr "Retry"
-#: src/app/main/ui/dashboard/team.cljs:500, src/app/main/ui/dashboard/team.cljs:746
+#: src/app/main/ui/dashboard/team.cljs:503, src/app/main/ui/dashboard/team.cljs:749
msgid "labels.role"
msgstr "Role"
@@ -2076,7 +2081,7 @@ msgstr "Sketch"
msgid "labels.start"
msgstr "Start"
-#: src/app/main/ui/dashboard/team.cljs:747
+#: src/app/main/ui/dashboard/team.cljs:750
msgid "labels.status"
msgstr "Status"
@@ -2096,11 +2101,11 @@ msgstr "Team member"
msgid "labels.themes"
msgstr "Themes"
-#: src/app/main/ui/dashboard/sidebar.cljs:992, src/app/main/ui/workspace/main_menu.cljs:122
+#: src/app/main/ui/dashboard/sidebar.cljs:992, src/app/main/ui/workspace/main_menu.cljs:123
msgid "labels.tutorials"
msgstr "Tutorials"
-#: src/app/main/ui/dashboard/file_menu.cljs:274
+#: src/app/main/ui/dashboard/file_menu.cljs:263
msgid "labels.unpublish-multi-files"
msgstr "Unpublish %s files"
@@ -2108,7 +2113,7 @@ msgstr "Unpublish %s files"
msgid "labels.update"
msgstr "Update"
-#: src/app/main/ui/dashboard/team_form.cljs:120
+#: src/app/main/ui/dashboard/team_form.cljs:122
msgid "labels.update-team"
msgstr "Update team"
@@ -2128,11 +2133,11 @@ msgstr "Uploading…"
msgid "labels.view-only"
msgstr "View only"
-#: src/app/main/ui/dashboard/team.cljs:125, src/app/main/ui/dashboard/team.cljs:304, src/app/main/ui/dashboard/team.cljs:552, src/app/main/ui/dashboard/team.cljs:588, src/app/main/ui/onboarding/team_choice.cljs:64
+#: src/app/main/ui/dashboard/team.cljs:125, src/app/main/ui/dashboard/team.cljs:307, src/app/main/ui/dashboard/team.cljs:555, src/app/main/ui/dashboard/team.cljs:591, src/app/main/ui/onboarding/team_choice.cljs:64
msgid "labels.viewer"
msgstr "Viewer"
-#: src/app/main/ui/dashboard/sidebar.cljs:523, src/app/main/ui/dashboard/team.cljs:97, src/app/main/ui/dashboard/team.cljs:107, src/app/main/ui/dashboard/team.cljs:918
+#: src/app/main/ui/dashboard/sidebar.cljs:523, src/app/main/ui/dashboard/team.cljs:97, src/app/main/ui/dashboard/team.cljs:107, src/app/main/ui/dashboard/team.cljs:921
msgid "labels.webhooks"
msgstr "Webhooks"
@@ -2188,24 +2193,24 @@ msgstr "Radial"
msgid "media.solid"
msgstr "Solid"
-#: src/app/main/data/common.cljs:130
+#: src/app/main/data/common.cljs:127
msgid "modals.add-shared-confirm-empty.hint"
msgstr ""
"Your library is empty. Once added as Shared Library, the assets you create "
"will be available to be used among the rest of your files. Are you sure you "
"want to publish it?"
-#: src/app/main/data/common.cljs:132
+#: src/app/main/data/common.cljs:129
msgid "modals.add-shared-confirm.accept"
msgstr "Add as Shared Library"
-#: src/app/main/data/common.cljs:130
+#: src/app/main/data/common.cljs:127
msgid "modals.add-shared-confirm.hint"
msgstr ""
"Once added as Shared Library, the assets of this file library will be "
"available to be used among the rest of your files."
-#: src/app/main/data/common.cljs:129
+#: src/app/main/data/common.cljs:126
msgid "modals.add-shared-confirm.message"
msgstr "Add “%s” as Shared Library"
@@ -2262,19 +2267,19 @@ msgstr "Generate access token"
msgid "modals.create-access-token.token"
msgstr ""
-#: src/app/main/ui/dashboard/team.cljs:911
+#: src/app/main/ui/dashboard/team.cljs:914
msgid "modals.create-webhook.submit-label"
msgstr "Create webhook"
-#: src/app/main/ui/dashboard/team.cljs:876
+#: src/app/main/ui/dashboard/team.cljs:879
msgid "modals.create-webhook.title"
msgstr "Create webhook"
-#: src/app/main/ui/dashboard/team.cljs:887
+#: src/app/main/ui/dashboard/team.cljs:890
msgid "modals.create-webhook.url.label"
msgstr "Payload URL"
-#: src/app/main/ui/dashboard/team.cljs:888
+#: src/app/main/ui/dashboard/team.cljs:891
msgid "modals.create-webhook.url.placeholder"
msgstr "https://example.com/postreceive"
@@ -2328,27 +2333,27 @@ msgstr "Are you sure you want to delete this annotation?"
msgid "modals.delete-component-annotation.title"
msgstr "Delete annotation"
-#: src/app/main/ui/dashboard/file_menu.cljs:128
+#: src/app/main/ui/dashboard/file_menu.cljs:125
msgid "modals.delete-file-confirm.accept"
msgstr "Delete file"
-#: src/app/main/ui/dashboard/file_menu.cljs:127
+#: src/app/main/ui/dashboard/file_menu.cljs:124
msgid "modals.delete-file-confirm.message"
msgstr "Are you sure you want to delete this file?"
-#: src/app/main/ui/dashboard/file_menu.cljs:126
+#: src/app/main/ui/dashboard/file_menu.cljs:123
msgid "modals.delete-file-confirm.title"
msgstr "Deleting file"
-#: src/app/main/ui/dashboard/file_menu.cljs:122
+#: src/app/main/ui/dashboard/file_menu.cljs:119
msgid "modals.delete-file-multi-confirm.accept"
msgstr "Delete files"
-#: src/app/main/ui/dashboard/file_menu.cljs:121
+#: src/app/main/ui/dashboard/file_menu.cljs:118
msgid "modals.delete-file-multi-confirm.message"
msgstr "Are you sure you want to delete %s files?"
-#: src/app/main/ui/dashboard/file_menu.cljs:120
+#: src/app/main/ui/dashboard/file_menu.cljs:117
msgid "modals.delete-file-multi-confirm.title"
msgstr "Deleting %s files"
@@ -2372,11 +2377,11 @@ msgstr ""
msgid "modals.delete-font.title"
msgstr "Deleting font"
-#: src/app/main/ui/workspace/context_menu.cljs:533, src/app/main/ui/workspace/sidebar/sitemap.cljs:46
+#: src/app/main/ui/workspace/context_menu.cljs:534, src/app/main/ui/workspace/sidebar/sitemap.cljs:46
msgid "modals.delete-page.body"
msgstr "Are you sure you want to delete this page?"
-#: src/app/main/ui/workspace/context_menu.cljs:532, src/app/main/ui/workspace/sidebar/sitemap.cljs:45
+#: src/app/main/ui/workspace/context_menu.cljs:533, src/app/main/ui/workspace/sidebar/sitemap.cljs:45
msgid "modals.delete-page.title"
msgstr "Delete page"
@@ -2436,15 +2441,15 @@ msgstr ""
msgid "modals.delete-team-confirm.title"
msgstr "Deleting team"
-#: src/app/main/ui/dashboard/team.cljs:457
+#: src/app/main/ui/dashboard/team.cljs:460
msgid "modals.delete-team-member-confirm.accept"
msgstr "Delete member"
-#: src/app/main/ui/dashboard/team.cljs:456
+#: src/app/main/ui/dashboard/team.cljs:459
msgid "modals.delete-team-member-confirm.message"
msgstr "Are you sure you want to delete this member from the team?"
-#: src/app/main/ui/dashboard/team.cljs:455
+#: src/app/main/ui/dashboard/team.cljs:458
msgid "modals.delete-team-member-confirm.title"
msgstr "Delete team member"
@@ -2458,23 +2463,23 @@ msgstr[1] ""
"Assets that have already been used in those files will remain there (no "
"design will be broken)."
-#: src/app/main/ui/dashboard/team.cljs:979
+#: src/app/main/ui/dashboard/team.cljs:982
msgid "modals.delete-webhook.accept"
msgstr "Delete webhook"
-#: src/app/main/ui/dashboard/team.cljs:978
+#: src/app/main/ui/dashboard/team.cljs:981
msgid "modals.delete-webhook.message"
msgstr "Are you sure you want to delete this webhook?"
-#: src/app/main/ui/dashboard/team.cljs:977
+#: src/app/main/ui/dashboard/team.cljs:980
msgid "modals.delete-webhook.title"
msgstr "Deleting webhook"
-#: src/app/main/ui/dashboard/team.cljs:910
+#: src/app/main/ui/dashboard/team.cljs:913
msgid "modals.edit-webhook.submit-label"
msgstr "Edit webhook"
-#: src/app/main/ui/dashboard/team.cljs:875
+#: src/app/main/ui/dashboard/team.cljs:878
msgid "modals.edit-webhook.title"
msgstr "Edit webhook"
@@ -2502,13 +2507,13 @@ msgstr ""
msgid "modals.invite-team-member.title"
msgstr "Invite members to the team"
-#: src/app/main/ui/dashboard/sidebar.cljs:423, src/app/main/ui/dashboard/team.cljs:423
+#: src/app/main/ui/dashboard/sidebar.cljs:423, src/app/main/ui/dashboard/team.cljs:426
msgid "modals.leave-and-close-confirm.hint"
msgstr ""
"As you're the only member of this team, the team will be deleted along with "
"its projects and files."
-#: src/app/main/ui/dashboard/sidebar.cljs:422, src/app/main/ui/dashboard/team.cljs:422
+#: src/app/main/ui/dashboard/sidebar.cljs:422, src/app/main/ui/dashboard/team.cljs:425
msgid "modals.leave-and-close-confirm.message"
msgstr "Are you sure you want to leave the %s team?"
@@ -2536,15 +2541,15 @@ msgstr "Select a member to promote"
msgid "modals.leave-and-reassign.title"
msgstr "Before you leave"
-#: src/app/main/ui/dashboard/sidebar.cljs:402, src/app/main/ui/dashboard/sidebar.cljs:424, src/app/main/ui/dashboard/team.cljs:424, src/app/main/ui/dashboard/team.cljs:446
+#: src/app/main/ui/dashboard/sidebar.cljs:402, src/app/main/ui/dashboard/sidebar.cljs:424, src/app/main/ui/dashboard/team.cljs:427, src/app/main/ui/dashboard/team.cljs:449
msgid "modals.leave-confirm.accept"
msgstr "Leave team"
-#: src/app/main/ui/dashboard/sidebar.cljs:401, src/app/main/ui/dashboard/team.cljs:445
+#: src/app/main/ui/dashboard/sidebar.cljs:401, src/app/main/ui/dashboard/team.cljs:448
msgid "modals.leave-confirm.message"
msgstr "Are you sure you want to leave this team?"
-#: src/app/main/ui/dashboard/sidebar.cljs:400, src/app/main/ui/dashboard/sidebar.cljs:421, src/app/main/ui/dashboard/team.cljs:421, src/app/main/ui/dashboard/team.cljs:444
+#: src/app/main/ui/dashboard/sidebar.cljs:400, src/app/main/ui/dashboard/sidebar.cljs:421, src/app/main/ui/dashboard/team.cljs:424, src/app/main/ui/dashboard/team.cljs:447
msgid "modals.leave-confirm.title"
msgstr "Leaving team"
@@ -2566,39 +2571,39 @@ msgid_plural "modals.move-shared-confirm.title"
msgstr[0] "Move library"
msgstr[1] "Move libraries"
-#: src/app/main/ui/workspace/main_menu.cljs:271, src/app/main/ui/workspace/nudge.cljs:47
+#: src/app/main/ui/workspace/main_menu.cljs:272, src/app/main/ui/workspace/nudge.cljs:47
msgid "modals.nudge-title"
msgstr "Nudge amount"
-#: src/app/main/ui/dashboard/team.cljs:370
+#: src/app/main/ui/dashboard/team.cljs:373
msgid "modals.promote-owner-confirm.accept"
msgstr "Transfer ownership"
-#: src/app/main/ui/dashboard/team.cljs:369
+#: src/app/main/ui/dashboard/team.cljs:372
msgid "modals.promote-owner-confirm.hint"
msgstr ""
"If you transfer the ownership, you will change your role to Admin, losing "
"some permissions over this team. "
-#: src/app/main/ui/dashboard/team.cljs:368
+#: src/app/main/ui/dashboard/team.cljs:371
msgid "modals.promote-owner-confirm.message"
msgstr ""
"You are the current owner of this team. Are you sure you want to make %s "
"the new owner of the team?"
-#: src/app/main/ui/dashboard/team.cljs:367
+#: src/app/main/ui/dashboard/team.cljs:370
msgid "modals.promote-owner-confirm.title"
msgstr "New team owner"
-#: src/app/main/ui/workspace/libraries.cljs:189
+#: src/app/main/ui/workspace/libraries.cljs:240
msgid "modals.publish-empty-library.accept"
msgstr "Publish"
-#: src/app/main/ui/workspace/libraries.cljs:188
+#: src/app/main/ui/workspace/libraries.cljs:239
msgid "modals.publish-empty-library.message"
msgstr "Your library is empty. Are you sure you want to publish it?"
-#: src/app/main/ui/workspace/libraries.cljs:187
+#: src/app/main/ui/workspace/libraries.cljs:238
msgid "modals.publish-empty-library.title"
msgstr "Publish empty library"
@@ -2653,21 +2658,21 @@ msgstr ""
msgid "modals.update-remote-component-in-bulk.message"
msgstr "Update components in a shared library"
-#: src/app/main/ui/workspace/sidebar/assets/common.cljs:380
+#: src/app/main/ui/workspace/sidebar/assets/common.cljs:384
msgid "modals.update-remote-component.accept"
msgstr "Update"
-#: src/app/main/ui/workspace/sidebar/assets/common.cljs:379
+#: src/app/main/ui/workspace/sidebar/assets/common.cljs:383
msgid "modals.update-remote-component.cancel"
msgstr "Cancel"
-#: src/app/main/ui/workspace/sidebar/assets/common.cljs:378
+#: src/app/main/ui/workspace/sidebar/assets/common.cljs:382
msgid "modals.update-remote-component.hint"
msgstr ""
"You are about to update a component in a shared library. This may affect "
"other files that use it."
-#: src/app/main/ui/workspace/sidebar/assets/common.cljs:377
+#: src/app/main/ui/workspace/sidebar/assets/common.cljs:381
msgid "modals.update-remote-component.message"
msgstr "Update a component in a shared library"
@@ -2771,19 +2776,19 @@ msgstr "To access this file, you can ask the team owner."
msgid "not-found.no-permission.you-can-ask.project"
msgstr "To access this project, you can ask the team owner."
-#: src/app/main/data/common.cljs:90
+#: src/app/main/data/common.cljs:87
msgid "notifications.by-code.maintenance"
msgstr "Maintenance break: we will be down for a short maintenance within 5 minutes."
-#: src/app/main/data/common.cljs:81
+#: src/app/main/data/common.cljs:78
msgid "notifications.by-code.upgrade-version"
msgstr "A new version is available, please refresh the page"
-#: src/app/main/ui/dashboard/team.cljs:164, src/app/main/ui/dashboard/team.cljs:631
+#: src/app/main/ui/dashboard/team.cljs:164, src/app/main/ui/dashboard/team.cljs:634
msgid "notifications.invitation-email-sent"
msgstr "Invitation sent successfully"
-#: src/app/main/ui/dashboard/team.cljs:652
+#: src/app/main/ui/dashboard/team.cljs:655
msgid "notifications.invitation-link-copied"
msgstr "Invitation link copied"
@@ -3147,7 +3152,7 @@ msgstr "Go to login"
msgid "settings.detach"
msgstr "Detach"
-#: src/app/main/ui/viewer/inspect/exports.cljs:155, src/app/main/ui/workspace/sidebar/options/menus/component.cljs:632, src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs:137, src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs:148, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs:204, src/app/main/ui/workspace/sidebar/options/menus/fill.cljs:161, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:470, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:476, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:494, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:500, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:526, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:537, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:554, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:569, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:576, src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs:312, src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs:182, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:378, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:395, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:248, src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs:172
+#: src/app/main/ui/viewer/inspect/exports.cljs:147, src/app/main/ui/workspace/sidebar/options/menus/component.cljs:632, src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs:137, src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs:148, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs:196, src/app/main/ui/workspace/sidebar/options/menus/fill.cljs:161, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:470, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:476, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:494, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:500, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:526, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:537, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:554, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:569, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:576, src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs:312, src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs:182, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:378, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:395, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:248, src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs:172
msgid "settings.multiple"
msgstr "Mixed"
@@ -3160,19 +3165,19 @@ msgid "settings.select-this-color"
msgstr "Select items using this style"
# SECTIONS
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:414
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:415
msgid "shortcut-section.basics"
msgstr "Basics"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:420
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:421
msgid "shortcut-section.dashboard"
msgstr "Dashboard"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:423
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:424
msgid "shortcut-section.viewer"
msgstr "Viewer"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:417
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:418
msgid "shortcut-section.workspace"
msgstr "Workspace"
@@ -3193,7 +3198,7 @@ msgstr "Generic"
msgid "shortcut-subsection.general-viewer"
msgstr "Generic"
-#: src/app/main/ui/workspace/main_menu.cljs:777, src/app/main/ui/workspace/sidebar/shortcuts.cljs:60
+#: src/app/main/ui/workspace/main_menu.cljs:826, src/app/main/ui/workspace/sidebar/shortcuts.cljs:60
msgid "shortcut-subsection.main-menu"
msgstr "Main menu"
@@ -3537,7 +3542,7 @@ msgstr "Move up"
msgid "shortcuts.next-frame"
msgstr "Next board"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:516
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:517
msgid "shortcuts.not-found"
msgstr "No shortcuts found"
@@ -3609,7 +3614,7 @@ msgstr "Go to viewer interactions section"
msgid "shortcuts.open-workspace"
msgstr "Go to workspace"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:260
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:261
msgid "shortcuts.or"
msgstr " or "
@@ -3625,203 +3630,204 @@ msgstr "Previous board"
msgid "shortcuts.redo"
msgstr "Redo"
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:166
msgid "shortcuts.rename"
msgstr "Rename"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:166
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:167
msgid "shortcuts.reset-zoom"
msgstr "Reset zoom"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:167
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:168
msgid "shortcuts.scale"
msgstr "Scale"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:168
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:169
msgid "shortcuts.search-placeholder"
msgstr "Search shortcuts"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:169
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:170
msgid "shortcuts.select-all"
msgstr "Select all"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:170
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:171
msgid "shortcuts.select-next"
msgstr "Select next layer"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:171
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:172
msgid "shortcuts.select-parent-layer"
msgstr "Select parent layer"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:172
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:173
msgid "shortcuts.select-prev"
msgstr "Select previous layer"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:173
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:174
msgid "shortcuts.separate-nodes"
msgstr "Separate nodes"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:174
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:175
msgid "shortcuts.show-pixel-grid"
msgstr "Show / Hide pixel grid"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:175
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:176
msgid "shortcuts.show-shortcuts"
msgstr "Show / Hide shortcuts"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:176
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:177
msgid "shortcuts.snap-nodes"
msgstr "Snap to nodes"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:177
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:178
msgid "shortcuts.snap-pixel-grid"
msgstr "Snap to pixel grid"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:178
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:179
msgid "shortcuts.start-editing"
msgstr "Start editing"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:179
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:180
msgid "shortcuts.start-measure"
msgstr "Start measurement"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:180
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:181
msgid "shortcuts.stop-measure"
msgstr "Stop measurement"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:181
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:182
msgid "shortcuts.text-align-center"
msgstr "Align center"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:182
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:183
msgid "shortcuts.text-align-justify"
msgstr "Align justify"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:183
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:184
msgid "shortcuts.text-align-left"
msgstr "Align left"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:184
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:185
msgid "shortcuts.text-align-right"
msgstr "Align right"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:185
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:186
msgid "shortcuts.thumbnail-set"
msgstr "Set thumbnails"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:496, src/app/main/ui/workspace/sidebar/shortcuts.cljs:505
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:497, src/app/main/ui/workspace/sidebar/shortcuts.cljs:506
msgid "shortcuts.title"
msgstr "Keyboard shortcuts"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:186
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:187
msgid "shortcuts.toggle-alignment"
msgstr "Toggle dynamic alignment"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:187
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:188
msgid "shortcuts.toggle-assets"
msgstr "Toggle assets"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:188
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:189
msgid "shortcuts.toggle-colorpalette"
msgstr "Toggle color palette"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:189
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:190
msgid "shortcuts.toggle-focus-mode"
msgstr "Toggle focus mode"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:190
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:191
msgid "shortcuts.toggle-fullscreen"
msgstr "Toggle fullscreen"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:191
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:192
msgid "shortcuts.toggle-guides"
msgstr "Show / Hide guides"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:192
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:193
msgid "shortcuts.toggle-history"
msgstr "Toggle history"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:193
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:194
msgid "shortcuts.toggle-layers"
msgstr "Toggle layers"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:194
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:195
msgid "shortcuts.toggle-layout-flex"
msgstr "Add / Remove flex layout"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:195
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:196
msgid "shortcuts.toggle-layout-grid"
msgstr "Add/remove grid layout"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:196
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:197
msgid "shortcuts.toggle-lock"
msgstr "Lock / Unlock"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:197
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:198
msgid "shortcuts.toggle-lock-size"
msgstr "Lock proportions"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:198
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:199
msgid "shortcuts.toggle-rulers"
msgstr "Show / Hide rulers"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:199
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:200
#, fuzzy
msgid "shortcuts.toggle-rules"
msgstr ""
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:200
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:201
msgid "shortcuts.toggle-snap-guides"
msgstr "Snap to guides"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:201
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:202
msgid "shortcuts.toggle-snap-ruler-guide"
msgstr "Snap to ruler guides"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:202
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:203
msgid "shortcuts.toggle-textpalette"
msgstr "Toggle text palette"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:203
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:204
msgid "shortcuts.toggle-theme"
msgstr "Change theme"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:204
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:205
msgid "shortcuts.toggle-visibility"
msgstr "Show / Hide"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:205
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:206
msgid "shortcuts.toggle-zoom-style"
msgstr "Toggle zoom style"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:206
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:207
msgid "shortcuts.underline"
msgstr "Toggle underline"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:207
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:208
msgid "shortcuts.undo"
msgstr "Undo"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:208
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:209
msgid "shortcuts.ungroup"
msgstr "Ungroup"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:209
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:210
msgid "shortcuts.unmask"
msgstr "Unmask"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:210
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:211
msgid "shortcuts.v-distribute"
msgstr "Distribute vertically"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:211
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:212
msgid "shortcuts.zoom-lense-decrease"
msgstr "Zoom lense decrease"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:212
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:213
msgid "shortcuts.zoom-lense-increase"
msgstr "Zoom lense increase"
-#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:213
+#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:214
msgid "shortcuts.zoom-selected"
msgstr "Zoom to selected"
@@ -3873,19 +3879,19 @@ msgstr "Password - Penpot"
msgid "title.settings.profile"
msgstr "Profile - Penpot"
-#: src/app/main/ui/dashboard/team.cljs:764
+#: src/app/main/ui/dashboard/team.cljs:767
msgid "title.team-invitations"
msgstr "Invitations - %s - Penpot"
-#: src/app/main/ui/dashboard/team.cljs:524
+#: src/app/main/ui/dashboard/team.cljs:527
msgid "title.team-members"
msgstr "Members - %s - Penpot"
-#: src/app/main/ui/dashboard/team.cljs:1074
+#: src/app/main/ui/dashboard/team.cljs:1077
msgid "title.team-settings"
msgstr "Settings - %s - Penpot"
-#: src/app/main/ui/dashboard/team.cljs:1027
+#: src/app/main/ui/dashboard/team.cljs:1030
msgid "title.team-webhooks"
msgstr "Webhooks - %s - Penpot"
@@ -3897,13 +3903,13 @@ msgstr "%s - View mode - Penpot"
msgid "title.workspace"
msgstr "%s - Penpot"
-#: src/app/main/ui.cljs:138
+#: src/app/main/ui.cljs:139
msgid "viewer.breaking-change.description"
msgstr ""
"This shareable link is no longer valid. Create a new one or ask the owner "
"for a new one."
-#: src/app/main/ui.cljs:137
+#: src/app/main/ui.cljs:138
msgid "viewer.breaking-change.message"
msgstr "Sorry!"
@@ -3955,7 +3961,7 @@ msgstr "Show interactions on click"
msgid "viewer.header.sitemap"
msgstr "Sitemap"
-#: src/app/main/ui/dashboard/team.cljs:985
+#: src/app/main/ui/dashboard/team.cljs:988
msgid "webhooks.last-delivery.success"
msgstr "Last delivery was successful."
@@ -3991,28 +3997,32 @@ msgstr "Distribute vertical spacing (%s)"
msgid "workspace.align.vtop"
msgstr "Align top (%s)"
+#: src/app/main/ui/workspace/sidebar/assets.cljs:171, src/app/main/ui/workspace/sidebar/assets.cljs:176
+msgid "workspace.assets.add-library"
+msgstr "Add library"
+
#: src/app/main/ui/workspace/sidebar/assets.cljs
#, unused
msgid "workspace.assets.assets"
msgstr "Assets"
-#: src/app/main/ui/workspace/sidebar/assets.cljs:135
+#: src/app/main/ui/workspace/sidebar/assets.cljs:143
msgid "workspace.assets.box-filter-all"
msgstr "All assets"
-#: src/app/main/ui/dashboard/grid.cljs:138, src/app/main/ui/dashboard/grid.cljs:170, src/app/main/ui/workspace/sidebar/assets/colors.cljs:487, src/app/main/ui/workspace/sidebar/assets.cljs:147
+#: src/app/main/ui/dashboard/grid.cljs:138, src/app/main/ui/dashboard/grid.cljs:170, src/app/main/ui/workspace/sidebar/assets/colors.cljs:492, src/app/main/ui/workspace/sidebar/assets.cljs:155
msgid "workspace.assets.colors"
msgstr "Colors"
-#: src/app/main/ui/workspace/sidebar/assets/colors.cljs:495
+#: src/app/main/ui/workspace/sidebar/assets/colors.cljs:500
msgid "workspace.assets.colors.add-color"
msgstr "Add color"
-#: src/app/main/ui/dashboard/grid.cljs:134, src/app/main/ui/dashboard/grid.cljs:149, src/app/main/ui/workspace/sidebar/assets/components.cljs:511, src/app/main/ui/workspace/sidebar/assets.cljs:138
+#: src/app/main/ui/dashboard/grid.cljs:134, src/app/main/ui/dashboard/grid.cljs:149, src/app/main/ui/workspace/sidebar/assets/components.cljs:502, src/app/main/ui/workspace/sidebar/assets.cljs:146
msgid "workspace.assets.components"
msgstr "Components"
-#: src/app/main/ui/workspace/sidebar/assets/components.cljs:532
+#: src/app/main/ui/workspace/sidebar/assets/components.cljs:523
msgid "workspace.assets.components.add-component"
msgstr "Add component"
@@ -4024,35 +4034,35 @@ msgstr "Create a group"
msgid "workspace.assets.create-group-hint"
msgstr "Your items are going to be named automatically as \"group name / item name\""
-#: src/app/main/ui/workspace/context_menu.cljs:540, src/app/main/ui/workspace/sidebar/assets/colors.cljs:251, src/app/main/ui/workspace/sidebar/assets/components.cljs:576, src/app/main/ui/workspace/sidebar/assets/graphics.cljs:424, src/app/main/ui/workspace/sidebar/assets/typographies.cljs:447
+#: src/app/main/ui/workspace/context_menu.cljs:543, src/app/main/ui/workspace/sidebar/assets/colors.cljs:256, src/app/main/ui/workspace/sidebar/assets/components.cljs:567, src/app/main/ui/workspace/sidebar/assets/graphics.cljs:424, src/app/main/ui/workspace/sidebar/assets/typographies.cljs:460
msgid "workspace.assets.delete"
msgstr "Delete"
-#: src/app/main/ui/workspace/context_menu.cljs:545, src/app/main/ui/workspace/sidebar/assets/components.cljs:571
+#: src/app/main/ui/workspace/context_menu.cljs:548, src/app/main/ui/workspace/sidebar/assets/components.cljs:562
msgid "workspace.assets.duplicate"
msgstr "Duplicate"
-#: src/app/main/ui/workspace/sidebar/assets/components.cljs:570
+#: src/app/main/ui/workspace/sidebar/assets/components.cljs:561
msgid "workspace.assets.duplicate-main"
msgstr "Duplicate main"
-#: src/app/main/ui/workspace/sidebar/assets/colors.cljs:247, src/app/main/ui/workspace/sidebar/assets/typographies.cljs:443
+#: src/app/main/ui/workspace/sidebar/assets/colors.cljs:252, src/app/main/ui/workspace/sidebar/assets/typographies.cljs:456
msgid "workspace.assets.edit"
msgstr "Edit"
-#: src/app/main/ui/workspace/sidebar/assets.cljs:171
+#: src/app/main/ui/workspace/sidebar/assets.cljs:192
msgid "workspace.assets.filter"
msgstr "Filter"
-#: src/app/main/ui/workspace/sidebar/assets/graphics.cljs:384, src/app/main/ui/workspace/sidebar/assets.cljs:143
+#: src/app/main/ui/workspace/sidebar/assets/graphics.cljs:384, src/app/main/ui/workspace/sidebar/assets.cljs:151
msgid "workspace.assets.graphics"
msgstr "Graphics"
-#: src/app/main/ui/workspace/sidebar/assets/components.cljs:527
+#: src/app/main/ui/workspace/sidebar/assets/components.cljs:518
msgid "workspace.assets.grid-view"
msgstr "Grid view"
-#: src/app/main/ui/workspace/sidebar/assets/colors.cljs:255, src/app/main/ui/workspace/sidebar/assets/components.cljs:580, src/app/main/ui/workspace/sidebar/assets/graphics.cljs:428, src/app/main/ui/workspace/sidebar/assets/typographies.cljs:452
+#: src/app/main/ui/workspace/sidebar/assets/colors.cljs:260, src/app/main/ui/workspace/sidebar/assets/components.cljs:571, src/app/main/ui/workspace/sidebar/assets/graphics.cljs:428, src/app/main/ui/workspace/sidebar/assets/typographies.cljs:465
msgid "workspace.assets.group"
msgstr "Group"
@@ -4060,30 +4070,27 @@ msgstr "Group"
msgid "workspace.assets.group-name"
msgstr "Group name"
-#: src/app/main/ui/workspace/sidebar/assets.cljs:163
+#: src/app/main/ui/workspace/sidebar/assets.cljs:183
msgid "workspace.assets.libraries"
msgstr "Libraries"
-msgid "workspace.assets.add-library"
-msgstr "Add library"
-
-#: src/app/main/ui/workspace/sidebar/assets/components.cljs:523
+#: src/app/main/ui/workspace/sidebar/assets/components.cljs:514
msgid "workspace.assets.list-view"
msgstr "List view"
-#: src/app/main/ui/workspace/sidebar/assets/file_library.cljs:62, src/app/main/ui/workspace/sidebar/options/menus/component.cljs:347
+#: src/app/main/ui/workspace/sidebar/assets/file_library.cljs:72, src/app/main/ui/workspace/sidebar/options/menus/component.cljs:347
msgid "workspace.assets.local-library"
msgstr "local library"
-#: src/app/main/ui/workspace/sidebar/assets/file_library.cljs:295
+#: src/app/main/ui/workspace/sidebar/assets/file_library.cljs:304
msgid "workspace.assets.not-found"
msgstr "No assets found"
-#: src/app/main/ui/workspace/sidebar/assets/file_library.cljs:68
+#: src/app/main/ui/workspace/sidebar/assets/file_library.cljs:78
msgid "workspace.assets.open-library"
msgstr "Open library file"
-#: src/app/main/ui/workspace/context_menu.cljs:543, src/app/main/ui/workspace/sidebar/assets/colors.cljs:243, src/app/main/ui/workspace/sidebar/assets/components.cljs:565, src/app/main/ui/workspace/sidebar/assets/graphics.cljs:421, src/app/main/ui/workspace/sidebar/assets/groups.cljs:62, src/app/main/ui/workspace/sidebar/assets/typographies.cljs:438
+#: src/app/main/ui/workspace/context_menu.cljs:546, src/app/main/ui/workspace/sidebar/assets/colors.cljs:248, src/app/main/ui/workspace/sidebar/assets/components.cljs:556, src/app/main/ui/workspace/sidebar/assets/graphics.cljs:421, src/app/main/ui/workspace/sidebar/assets/groups.cljs:62, src/app/main/ui/workspace/sidebar/assets/typographies.cljs:451
msgid "workspace.assets.rename"
msgstr "Rename"
@@ -4091,7 +4098,7 @@ msgstr "Rename"
msgid "workspace.assets.rename-group"
msgstr "Rename group"
-#: src/app/main/ui/workspace/sidebar/assets.cljs:168
+#: src/app/main/ui/workspace/sidebar/assets.cljs:189
msgid "workspace.assets.search"
msgstr "Search assets"
@@ -4113,15 +4120,15 @@ msgid_plural "workspace.assets.sidebar.components"
msgstr[0] "1 component"
msgstr[1] "%s components"
-#: src/app/main/ui/workspace/sidebar/assets.cljs:187
+#: src/app/main/ui/workspace/sidebar/assets.cljs:208
msgid "workspace.assets.sort"
msgstr "Sort"
-#: src/app/main/ui/dashboard/grid.cljs:142, src/app/main/ui/dashboard/grid.cljs:197, src/app/main/ui/workspace/sidebar/assets/typographies.cljs:400, src/app/main/ui/workspace/sidebar/assets.cljs:151
+#: src/app/main/ui/dashboard/grid.cljs:142, src/app/main/ui/dashboard/grid.cljs:197, src/app/main/ui/workspace/sidebar/assets/typographies.cljs:413, src/app/main/ui/workspace/sidebar/assets.cljs:159
msgid "workspace.assets.typography"
msgstr "Typographies"
-#: src/app/main/ui/workspace/sidebar/assets/typographies.cljs:408
+#: src/app/main/ui/workspace/sidebar/assets/typographies.cljs:421
msgid "workspace.assets.typography.add-typography"
msgstr "Add typography"
@@ -4150,7 +4157,7 @@ msgstr "Letter Spacing"
msgid "workspace.assets.typography.line-height"
msgstr "Line Height"
-#: src/app/main/ui/dashboard/grid.cljs:207, src/app/main/ui/workspace/libraries.cljs:469, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:474, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:499, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:606, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:625
+#: src/app/main/ui/dashboard/grid.cljs:207, src/app/main/ui/workspace/libraries.cljs:537, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:474, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:499, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:606, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:625
msgid "workspace.assets.typography.sample"
msgstr "Ag"
@@ -4166,67 +4173,67 @@ msgstr "Text Transform"
msgid "workspace.assets.ungroup"
msgstr "Ungroup"
-#: src/app/main/ui/workspace/context_menu.cljs:648
+#: src/app/main/ui/workspace/context_menu.cljs:651
msgid "workspace.context-menu.grid-cells.area"
msgstr "Create area"
-#: src/app/main/ui/workspace/context_menu.cljs:651
+#: src/app/main/ui/workspace/context_menu.cljs:654
msgid "workspace.context-menu.grid-cells.create-board"
msgstr "Create board"
-#: src/app/main/ui/workspace/context_menu.cljs:643
+#: src/app/main/ui/workspace/context_menu.cljs:646
msgid "workspace.context-menu.grid-cells.merge"
msgstr "Merge cells"
-#: src/app/main/ui/workspace/context_menu.cljs:608
+#: src/app/main/ui/workspace/context_menu.cljs:611
msgid "workspace.context-menu.grid-track.column.add-after"
msgstr "Add 1 column to the right"
-#: src/app/main/ui/workspace/context_menu.cljs:607
+#: src/app/main/ui/workspace/context_menu.cljs:610
msgid "workspace.context-menu.grid-track.column.add-before"
msgstr "Add 1 column to the left"
-#: src/app/main/ui/workspace/context_menu.cljs:609
+#: src/app/main/ui/workspace/context_menu.cljs:612
msgid "workspace.context-menu.grid-track.column.delete"
msgstr "Delete column"
-#: src/app/main/ui/workspace/context_menu.cljs:610
+#: src/app/main/ui/workspace/context_menu.cljs:613
msgid "workspace.context-menu.grid-track.column.delete-shapes"
msgstr "Delete column and shapes"
-#: src/app/main/ui/workspace/context_menu.cljs:606
+#: src/app/main/ui/workspace/context_menu.cljs:609
msgid "workspace.context-menu.grid-track.column.duplicate"
msgstr "Duplicate column"
-#: src/app/main/ui/workspace/context_menu.cljs:615
+#: src/app/main/ui/workspace/context_menu.cljs:618
msgid "workspace.context-menu.grid-track.row.add-after"
msgstr "Add 1 row below"
-#: src/app/main/ui/workspace/context_menu.cljs:614
+#: src/app/main/ui/workspace/context_menu.cljs:617
msgid "workspace.context-menu.grid-track.row.add-before"
msgstr "Add 1 row above"
-#: src/app/main/ui/workspace/context_menu.cljs:616
+#: src/app/main/ui/workspace/context_menu.cljs:619
msgid "workspace.context-menu.grid-track.row.delete"
msgstr "Delete row"
-#: src/app/main/ui/workspace/context_menu.cljs:617
+#: src/app/main/ui/workspace/context_menu.cljs:620
msgid "workspace.context-menu.grid-track.row.delete-shapes"
msgstr "Delete row and shapes"
-#: src/app/main/ui/workspace/context_menu.cljs:613
+#: src/app/main/ui/workspace/context_menu.cljs:616
msgid "workspace.context-menu.grid-track.row.duplicate"
msgstr "Duplicate row"
-#: src/app/main/ui/workspace/sidebar/layers.cljs:527
+#: src/app/main/ui/workspace/sidebar/layers.cljs:528
msgid "workspace.focus.focus-mode"
msgstr "Focus mode"
-#: src/app/main/ui/workspace/context_menu.cljs:298, src/app/main/ui/workspace/context_menu.cljs:567
+#: src/app/main/ui/workspace/context_menu.cljs:299, src/app/main/ui/workspace/context_menu.cljs:570
msgid "workspace.focus.focus-off"
msgstr "Focus off"
-#: src/app/main/ui/workspace/context_menu.cljs:297
+#: src/app/main/ui/workspace/context_menu.cljs:298
msgid "workspace.focus.focus-on"
msgstr "Focus on"
@@ -4242,11 +4249,11 @@ msgstr "Linear gradient"
msgid "workspace.gradients.radial"
msgstr "Radial gradient"
-#: src/app/main/ui/workspace/main_menu.cljs:243
+#: src/app/main/ui/workspace/main_menu.cljs:244
msgid "workspace.header.menu.disable-dynamic-alignment"
msgstr "Disable dynamic alignment"
-#: src/app/main/ui/workspace/main_menu.cljs:197
+#: src/app/main/ui/workspace/main_menu.cljs:198
msgid "workspace.header.menu.disable-scale-content"
msgstr "Disable proportional scale"
@@ -4255,23 +4262,23 @@ msgstr "Disable proportional scale"
msgid "workspace.header.menu.disable-scale-text"
msgstr "Disable scale text"
-#: src/app/main/ui/workspace/main_menu.cljs:228
+#: src/app/main/ui/workspace/main_menu.cljs:229
msgid "workspace.header.menu.disable-snap-guides"
msgstr "Disable snap to guides"
-#: src/app/main/ui/workspace/main_menu.cljs:258
+#: src/app/main/ui/workspace/main_menu.cljs:259
msgid "workspace.header.menu.disable-snap-pixel-grid"
msgstr "Disable snap to pixel"
-#: src/app/main/ui/workspace/main_menu.cljs:212
+#: src/app/main/ui/workspace/main_menu.cljs:213
msgid "workspace.header.menu.disable-snap-ruler-guides"
msgstr "Disable snap to ruler guides"
-#: src/app/main/ui/workspace/main_menu.cljs:244
+#: src/app/main/ui/workspace/main_menu.cljs:245
msgid "workspace.header.menu.enable-dynamic-alignment"
msgstr "Enable dynamic alignment"
-#: src/app/main/ui/workspace/main_menu.cljs:198
+#: src/app/main/ui/workspace/main_menu.cljs:199
msgid "workspace.header.menu.enable-scale-content"
msgstr "Enable proportional scale"
@@ -4280,103 +4287,103 @@ msgstr "Enable proportional scale"
msgid "workspace.header.menu.enable-scale-text"
msgstr "Enable scale text"
-#: src/app/main/ui/workspace/main_menu.cljs:229
+#: src/app/main/ui/workspace/main_menu.cljs:230
msgid "workspace.header.menu.enable-snap-guides"
msgstr "Snap to guides"
-#: src/app/main/ui/workspace/main_menu.cljs:259
+#: src/app/main/ui/workspace/main_menu.cljs:260
msgid "workspace.header.menu.enable-snap-pixel-grid"
msgstr "Enable snap to pixel"
-#: src/app/main/ui/workspace/main_menu.cljs:213
+#: src/app/main/ui/workspace/main_menu.cljs:214
msgid "workspace.header.menu.enable-snap-ruler-guides"
msgstr "Snap to ruler guides"
-#: src/app/main/ui/workspace/main_menu.cljs:388
+#: src/app/main/ui/workspace/main_menu.cljs:389
msgid "workspace.header.menu.hide-artboard-names"
msgstr "Hide board names"
-#: src/app/main/ui/workspace/main_menu.cljs:342
+#: src/app/main/ui/workspace/main_menu.cljs:343
msgid "workspace.header.menu.hide-guides"
msgstr "Hide guides"
-#: src/app/main/ui/workspace/main_menu.cljs:359
+#: src/app/main/ui/workspace/main_menu.cljs:360
msgid "workspace.header.menu.hide-palette"
msgstr "Hide color palette"
-#: src/app/main/ui/workspace/main_menu.cljs:400
+#: src/app/main/ui/workspace/main_menu.cljs:401
msgid "workspace.header.menu.hide-pixel-grid"
msgstr "Hide pixel grid"
-#: src/app/main/ui/workspace/main_menu.cljs:326
+#: src/app/main/ui/workspace/main_menu.cljs:327
msgid "workspace.header.menu.hide-rules"
msgstr "Hide rulers"
-#: src/app/main/ui/workspace/main_menu.cljs:373
+#: src/app/main/ui/workspace/main_menu.cljs:374
msgid "workspace.header.menu.hide-textpalette"
msgstr "Hide fonts palette"
-#: src/app/main/ui/workspace/main_menu.cljs:803
+#: src/app/main/ui/workspace/main_menu.cljs:852
msgid "workspace.header.menu.option.edit"
msgstr "Edit"
-#: src/app/main/ui/workspace/main_menu.cljs:792
+#: src/app/main/ui/workspace/main_menu.cljs:841
msgid "workspace.header.menu.option.file"
msgstr "File"
-#: src/app/main/ui/workspace/main_menu.cljs:849
+#: src/app/main/ui/workspace/main_menu.cljs:898
msgid "workspace.header.menu.option.help-info"
msgstr "Help & info"
-#: src/app/main/ui/workspace/main_menu.cljs:825
+#: src/app/main/ui/workspace/main_menu.cljs:874
msgid "workspace.header.menu.option.preferences"
msgstr "Preferences"
-#: src/app/main/ui/workspace/main_menu.cljs:814
+#: src/app/main/ui/workspace/main_menu.cljs:863
msgid "workspace.header.menu.option.view"
msgstr "View"
-#: src/app/main/ui/workspace/main_menu.cljs:471
+#: src/app/main/ui/workspace/main_menu.cljs:472
msgid "workspace.header.menu.redo"
msgstr "Redo"
-#: src/app/main/ui/workspace/main_menu.cljs:442
+#: src/app/main/ui/workspace/main_menu.cljs:443
msgid "workspace.header.menu.select-all"
msgstr "Select all"
-#: src/app/main/ui/workspace/main_menu.cljs:389
+#: src/app/main/ui/workspace/main_menu.cljs:390
msgid "workspace.header.menu.show-artboard-names"
msgstr "Show boards names"
-#: src/app/main/ui/workspace/main_menu.cljs:343
+#: src/app/main/ui/workspace/main_menu.cljs:344
msgid "workspace.header.menu.show-guides"
msgstr "Show guides"
-#: src/app/main/ui/workspace/main_menu.cljs:360
+#: src/app/main/ui/workspace/main_menu.cljs:361
msgid "workspace.header.menu.show-palette"
msgstr "Show color palette"
-#: src/app/main/ui/workspace/main_menu.cljs:401
+#: src/app/main/ui/workspace/main_menu.cljs:402
msgid "workspace.header.menu.show-pixel-grid"
msgstr "Show pixel grid"
-#: src/app/main/ui/workspace/main_menu.cljs:327
+#: src/app/main/ui/workspace/main_menu.cljs:328
msgid "workspace.header.menu.show-rules"
msgstr "Show rulers"
-#: src/app/main/ui/workspace/main_menu.cljs:374
+#: src/app/main/ui/workspace/main_menu.cljs:375
msgid "workspace.header.menu.show-textpalette"
msgstr "Show fonts palette"
-#: src/app/main/ui/workspace/main_menu.cljs:284
+#: src/app/main/ui/workspace/main_menu.cljs:285
msgid "workspace.header.menu.toggle-dark-theme"
msgstr "Switch to dark theme"
-#: src/app/main/ui/workspace/main_menu.cljs:283
+#: src/app/main/ui/workspace/main_menu.cljs:284
msgid "workspace.header.menu.toggle-light-theme"
msgstr "Switch to light theme"
-#: src/app/main/ui/workspace/main_menu.cljs:457
+#: src/app/main/ui/workspace/main_menu.cljs:458
msgid "workspace.header.menu.undo"
msgstr "Undo"
@@ -4398,7 +4405,7 @@ msgstr "Saved"
msgid "workspace.header.saving"
msgstr "Saving"
-#: src/app/main/ui/workspace/right_header.cljs:255
+#: src/app/main/ui/workspace/right_header.cljs:257
msgid "workspace.header.share"
msgstr "Share"
@@ -4406,7 +4413,7 @@ msgstr "Share"
msgid "workspace.header.unsaved"
msgstr "Unsaved changes"
-#: src/app/main/ui/workspace/right_header.cljs:260
+#: src/app/main/ui/workspace/right_header.cljs:262
msgid "workspace.header.viewer"
msgstr "View mode (%s)"
@@ -4467,19 +4474,19 @@ msgstr "Locate grid layout"
msgid "workspace.libraries.add"
msgstr "Add"
-#: src/app/main/ui/workspace/libraries.cljs:81, src/app/main/ui/workspace/libraries.cljs:100
+#: src/app/main/ui/workspace/libraries.cljs:84, src/app/main/ui/workspace/libraries.cljs:105
msgid "workspace.libraries.colors"
msgstr "%s colors"
-#: src/app/main/ui/workspace/color_palette.cljs:129
+#: src/app/main/ui/workspace/color_palette.cljs:137
msgid "workspace.libraries.colors.empty-palette"
msgstr "There are no color styles in your library yet"
-#: src/app/main/ui/workspace/text_palette.cljs:153
+#: src/app/main/ui/workspace/text_palette.cljs:161
msgid "workspace.libraries.colors.empty-typography-palette"
msgstr "There are no typography styles in your library yet"
-#: src/app/main/ui/workspace/color_palette_ctx_menu.cljs:60, src/app/main/ui/workspace/colorpicker/libraries.cljs:73, src/app/main/ui/workspace/text_palette_ctx_menu.cljs:50
+#: src/app/main/ui/workspace/color_palette_ctx_menu.cljs:60, src/app/main/ui/workspace/colorpicker/libraries.cljs:74, src/app/main/ui/workspace/text_palette_ctx_menu.cljs:50
msgid "workspace.libraries.colors.file-library"
msgstr "File library"
@@ -4488,7 +4495,7 @@ msgstr "File library"
msgid "workspace.libraries.colors.hsv"
msgstr "HSV"
-#: src/app/main/ui/workspace/color_palette_ctx_menu.cljs:82, src/app/main/ui/workspace/colorpicker/libraries.cljs:72
+#: src/app/main/ui/workspace/color_palette_ctx_menu.cljs:82, src/app/main/ui/workspace/colorpicker/libraries.cljs:73
msgid "workspace.libraries.colors.recent-colors"
msgstr "Recent colors"
@@ -4505,23 +4512,35 @@ msgstr "RGBA"
msgid "workspace.libraries.colors.save-color"
msgstr "Save color style"
-#: src/app/main/ui/workspace/libraries.cljs:75, src/app/main/ui/workspace/libraries.cljs:92
+#: src/app/main/ui/workspace/libraries.cljs:78, src/app/main/ui/workspace/libraries.cljs:97
msgid "workspace.libraries.components"
msgstr "%s components"
-#: src/app/main/ui/workspace/libraries.cljs:216
+#: src/app/main/ui/workspace/libraries.cljs:353
+msgid "workspace.libraries.empty.add-some"
+msgstr "Or add some of these to try:"
+
+#: src/app/main/ui/workspace/libraries.cljs:347
+msgid "workspace.libraries.empty.no-libraries"
+msgstr "There are no Shared Libraries at you team, you can look for"
+
+#: src/app/main/ui/workspace/libraries.cljs:351
+msgid "workspace.libraries.empty.some-templates"
+msgstr "some templates in here"
+
+#: src/app/main/ui/workspace/libraries.cljs:267
msgid "workspace.libraries.file-library"
msgstr "File library"
-#: src/app/main/ui/workspace/libraries.cljs:78, src/app/main/ui/workspace/libraries.cljs:96
+#: src/app/main/ui/workspace/libraries.cljs:81, src/app/main/ui/workspace/libraries.cljs:101
msgid "workspace.libraries.graphics"
msgstr "%s graphics"
-#: src/app/main/ui/workspace/libraries.cljs:210
+#: src/app/main/ui/workspace/libraries.cljs:261
msgid "workspace.libraries.in-this-file"
msgstr "LIBRARIES IN THIS FILE"
-#: src/app/main/ui/workspace/libraries.cljs:517, src/app/main/ui/workspace/libraries.cljs:542
+#: src/app/main/ui/workspace/libraries.cljs:585, src/app/main/ui/workspace/libraries.cljs:611
msgid "workspace.libraries.libraries"
msgstr "LIBRARIES"
@@ -4530,43 +4549,43 @@ msgstr "LIBRARIES"
msgid "workspace.libraries.library"
msgstr "LIBRARY"
-#: src/app/main/ui/workspace/libraries.cljs:390
+#: src/app/main/ui/workspace/libraries.cljs:458
msgid "workspace.libraries.library-updates"
msgstr "LIBRARY UPDATES"
-#: src/app/main/ui/workspace/libraries.cljs:291
+#: src/app/main/ui/workspace/libraries.cljs:342
msgid "workspace.libraries.loading"
msgstr "Loading…"
-#: src/app/main/ui/workspace/libraries.cljs:300
+#: src/app/main/ui/workspace/libraries.cljs:368
msgid "workspace.libraries.more-templates"
msgstr "You can look for "
-#: src/app/main/ui/workspace/libraries.cljs:304
+#: src/app/main/ui/workspace/libraries.cljs:372
msgid "workspace.libraries.more-templates-link"
msgstr "more templates in here"
-#: src/app/main/ui/workspace/libraries.cljs:388
+#: src/app/main/ui/workspace/libraries.cljs:456
msgid "workspace.libraries.no-libraries-need-sync"
msgstr "There are no Shared Libraries that need update"
-#: src/app/main/ui/workspace/libraries.cljs:307
+#: src/app/main/ui/workspace/libraries.cljs:375
msgid "workspace.libraries.no-matches-for"
msgstr "No matches found for “%s“"
-#: src/app/main/ui/workspace/libraries.cljs:297
+#: src/app/main/ui/workspace/libraries.cljs:365
msgid "workspace.libraries.no-shared-libraries-available"
msgstr "There are no Shared Libraries available"
-#: src/app/main/ui/workspace/libraries.cljs:261
+#: src/app/main/ui/workspace/libraries.cljs:312
msgid "workspace.libraries.search-shared-libraries"
msgstr "Search shared libraries"
-#: src/app/main/ui/workspace/libraries.cljs:257
+#: src/app/main/ui/workspace/libraries.cljs:308
msgid "workspace.libraries.shared-libraries"
msgstr "SHARED LIBRARIES"
-#: src/app/main/ui/workspace/libraries.cljs:283
+#: src/app/main/ui/workspace/libraries.cljs:334
msgid "workspace.libraries.shared-library-btn"
msgstr "Connect library"
@@ -4578,35 +4597,26 @@ msgstr "Multiple typographies"
msgid "workspace.libraries.text.multiple-typography-tooltip"
msgstr "Unlink all typographies"
-#: src/app/main/ui/workspace/libraries.cljs:84, src/app/main/ui/workspace/libraries.cljs:104
+#: src/app/main/ui/workspace/libraries.cljs:87, src/app/main/ui/workspace/libraries.cljs:109
msgid "workspace.libraries.typography"
msgstr "%s typographies"
-#: src/app/main/ui/workspace/libraries.cljs:250
+#: src/app/main/ui/workspace/libraries.cljs:301
msgid "workspace.libraries.unlink-library-btn"
msgstr "Disconnect library"
-#: src/app/main/ui/workspace/libraries.cljs:410
+#: src/app/main/ui/workspace/libraries.cljs:478
msgid "workspace.libraries.update"
msgstr "Update"
-#: src/app/main/ui/workspace/libraries.cljs:485
+#: src/app/main/ui/workspace/libraries.cljs:553
msgid "workspace.libraries.update.see-all-changes"
msgstr "see all changes"
-#: src/app/main/ui/workspace/libraries.cljs:524
+#: src/app/main/ui/workspace/libraries.cljs:593
msgid "workspace.libraries.updates"
msgstr "UPDATES"
-msgid "workspace.libraries.empty.no-libraries"
-msgstr "There are no Shared Libraries at you team, you can look for"
-
-msgid "workspace.libraries.empty.some-templates"
-msgstr "some templates in here"
-
-msgid "workspace.libraries.empty.add-some"
-msgstr "Or add some of these to try:"
-
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs:745
msgid "workspace.options.add-interaction"
msgstr "Click the + button to add interactions."
@@ -4719,7 +4729,7 @@ msgstr "Top & Bottom"
msgid "workspace.options.design"
msgstr "Design"
-#: src/app/main/ui/viewer/inspect/exports.cljs:147
+#: src/app/main/ui/viewer/inspect/exports.cljs:139
msgid "workspace.options.export"
msgstr "Export"
@@ -4728,37 +4738,37 @@ msgstr "Export"
msgid "workspace.options.export-multiple"
msgstr "Export selection"
-#: src/app/main/ui/viewer/inspect/exports.cljs:203, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs:256
+#: src/app/main/ui/viewer/inspect/exports.cljs:195, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs:248
msgid "workspace.options.export-object"
msgid_plural "workspace.options.export-object"
msgstr[0] "Export 1 element"
msgstr[1] "Export %s elements"
-#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs:195
+#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs:187
msgid "workspace.options.export.add-export"
msgstr "Add export"
-#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs:207, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs:242
+#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs:199, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs:234
msgid "workspace.options.export.remove-export"
msgstr "Remove export"
-#: src/app/main/ui/viewer/inspect/exports.cljs:186, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs:236
+#: src/app/main/ui/viewer/inspect/exports.cljs:178, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs:228
msgid "workspace.options.export.suffix"
msgstr "Suffix"
-#: src/app/main/ui/exports/assets.cljs:246
+#: src/app/main/ui/exports/assets.cljs:239
msgid "workspace.options.exporting-complete"
msgstr "Export complete"
-#: src/app/main/ui/exports/assets.cljs:176, src/app/main/ui/exports/assets.cljs:247, src/app/main/ui/viewer/inspect/exports.cljs:202, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs:255
+#: src/app/main/ui/exports/assets.cljs:169, src/app/main/ui/exports/assets.cljs:240, src/app/main/ui/viewer/inspect/exports.cljs:194, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs:247
msgid "workspace.options.exporting-object"
msgstr "Exporting…"
-#: src/app/main/ui/exports/assets.cljs:245
+#: src/app/main/ui/exports/assets.cljs:238
msgid "workspace.options.exporting-object-error"
msgstr "Export failed"
-#: src/app/main/ui/exports/assets.cljs:248
+#: src/app/main/ui/exports/assets.cljs:241
msgid "workspace.options.exporting-object-slow"
msgstr "Export unexpectedly slow"
@@ -5414,7 +5424,7 @@ msgstr "Independent corners"
msgid "workspace.options.recent-fonts"
msgstr "Recent"
-#: src/app/main/ui/exports/assets.cljs:290
+#: src/app/main/ui/exports/assets.cljs:283
msgid "workspace.options.retry"
msgstr "Retry"
@@ -5784,7 +5794,7 @@ msgstr "No plugins installed yet"
msgid "workspace.plugins.error.manifest"
msgstr "The plugin manifest is incorrect."
-#: src/app/main/data/plugins.cljs:86, src/app/main/ui/workspace/main_menu.cljs:696, src/app/main/ui/workspace/plugins.cljs:82
+#: src/app/main/data/plugins.cljs:86, src/app/main/ui/workspace/main_menu.cljs:745, src/app/main/ui/workspace/plugins.cljs:82
msgid "workspace.plugins.error.need-editor"
msgstr "You need to be an editor to use this plugin"
@@ -5800,11 +5810,11 @@ msgstr "Install"
msgid "workspace.plugins.installed-plugins"
msgstr "Installed plugins"
-#: src/app/main/ui/workspace/main_menu.cljs:651
+#: src/app/main/ui/workspace/main_menu.cljs:700
msgid "workspace.plugins.menu.plugins-manager"
msgstr "Plugins manager"
-#: src/app/main/ui/workspace/main_menu.cljs:837
+#: src/app/main/ui/workspace/main_menu.cljs:886
msgid "workspace.plugins.menu.title"
msgstr "Plugins"
@@ -5899,11 +5909,11 @@ msgstr "'%s' PLUGIN IS INSTALLED FOR YOUR USER!"
msgid "workspace.plugins.try-out.try"
msgstr "TRY PLUGIN"
-#: src/app/main/ui/workspace/context_menu.cljs:451
+#: src/app/main/ui/workspace/context_menu.cljs:452
msgid "workspace.shape.menu.add-flex"
msgstr "Add flex layout"
-#: src/app/main/ui/workspace/context_menu.cljs:455
+#: src/app/main/ui/workspace/context_menu.cljs:456
msgid "workspace.shape.menu.add-grid"
msgstr "Add grid layout"
@@ -5911,91 +5921,91 @@ msgstr "Add grid layout"
msgid "workspace.shape.menu.add-layout"
msgstr "Add layout"
-#: src/app/main/ui/workspace/context_menu.cljs:194
+#: src/app/main/ui/workspace/context_menu.cljs:195
msgid "workspace.shape.menu.back"
msgstr "Send to back"
-#: src/app/main/ui/workspace/context_menu.cljs:191
+#: src/app/main/ui/workspace/context_menu.cljs:192
msgid "workspace.shape.menu.backward"
msgstr "Send backward"
-#: src/app/main/ui/workspace/context_menu.cljs:140
+#: src/app/main/ui/workspace/context_menu.cljs:141
msgid "workspace.shape.menu.copy"
msgstr "Copy"
-#: src/app/main/ui/workspace/sidebar/assets/common.cljs:427
+#: src/app/main/ui/workspace/sidebar/assets/common.cljs:431
msgid "workspace.shape.menu.create-annotation"
msgstr "Create annotation"
-#: src/app/main/ui/workspace/context_menu.cljs:286
+#: src/app/main/ui/workspace/context_menu.cljs:287
msgid "workspace.shape.menu.create-artboard-from-selection"
msgstr "Selection to board"
-#: src/app/main/ui/workspace/context_menu.cljs:475
+#: src/app/main/ui/workspace/context_menu.cljs:476
msgid "workspace.shape.menu.create-component"
msgstr "Create component"
-#: src/app/main/ui/workspace/context_menu.cljs:479
+#: src/app/main/ui/workspace/context_menu.cljs:480
msgid "workspace.shape.menu.create-multiple-components"
msgstr "Create multiple components"
-#: src/app/main/ui/workspace/context_menu.cljs:143
+#: src/app/main/ui/workspace/context_menu.cljs:144
msgid "workspace.shape.menu.cut"
msgstr "Cut"
-#: src/app/main/ui/workspace/context_menu.cljs:496, src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs:764, src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs:1052
+#: src/app/main/ui/workspace/context_menu.cljs:497, src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs:764, src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs:1052
msgid "workspace.shape.menu.delete"
msgstr "Delete"
-#: src/app/main/ui/workspace/context_menu.cljs:402
+#: src/app/main/ui/workspace/context_menu.cljs:403
msgid "workspace.shape.menu.delete-flow-start"
msgstr "Delete flow start"
-#: src/app/main/ui/workspace/sidebar/assets/common.cljs:432
+#: src/app/main/ui/workspace/sidebar/assets/common.cljs:436
msgid "workspace.shape.menu.detach-instance"
msgstr "Detach instance"
-#: src/app/main/ui/workspace/sidebar/assets/common.cljs:431
+#: src/app/main/ui/workspace/sidebar/assets/common.cljs:435
msgid "workspace.shape.menu.detach-instances-in-bulk"
msgstr "Detach instances"
-#: src/app/main/ui/workspace/context_menu.cljs:346, src/app/main/ui/workspace/sidebar/options/menus/bool.cljs:75
+#: src/app/main/ui/workspace/context_menu.cljs:347, src/app/main/ui/workspace/sidebar/options/menus/bool.cljs:75
msgid "workspace.shape.menu.difference"
msgstr "Difference"
-#: src/app/main/ui/workspace/context_menu.cljs:149
+#: src/app/main/ui/workspace/context_menu.cljs:150
msgid "workspace.shape.menu.duplicate"
msgstr "Duplicate"
-#: src/app/main/ui/workspace/context_menu.cljs:332
+#: src/app/main/ui/workspace/context_menu.cljs:333
msgid "workspace.shape.menu.edit"
msgstr "Edit"
-#: src/app/main/ui/workspace/context_menu.cljs:352
+#: src/app/main/ui/workspace/context_menu.cljs:353
msgid "workspace.shape.menu.exclude"
msgstr "Exclude"
-#: src/app/main/ui/workspace/context_menu.cljs:359, src/app/main/ui/workspace/sidebar/options/menus/bool.cljs:89
+#: src/app/main/ui/workspace/context_menu.cljs:360, src/app/main/ui/workspace/sidebar/options/menus/bool.cljs:89
msgid "workspace.shape.menu.flatten"
msgstr "Flatten"
-#: src/app/main/ui/workspace/context_menu.cljs:209
+#: src/app/main/ui/workspace/context_menu.cljs:210
msgid "workspace.shape.menu.flip-horizontal"
msgstr "Flip horizontal"
-#: src/app/main/ui/workspace/context_menu.cljs:205
+#: src/app/main/ui/workspace/context_menu.cljs:206
msgid "workspace.shape.menu.flip-vertical"
msgstr "Flip vertical"
-#: src/app/main/ui/workspace/context_menu.cljs:404
+#: src/app/main/ui/workspace/context_menu.cljs:405
msgid "workspace.shape.menu.flow-start"
msgstr "Flow start"
-#: src/app/main/ui/workspace/context_menu.cljs:185
+#: src/app/main/ui/workspace/context_menu.cljs:186
msgid "workspace.shape.menu.forward"
msgstr "Bring forward"
-#: src/app/main/ui/workspace/context_menu.cljs:188
+#: src/app/main/ui/workspace/context_menu.cljs:189
msgid "workspace.shape.menu.front"
msgstr "Bring to front"
@@ -6004,43 +6014,43 @@ msgstr "Bring to front"
msgid "workspace.shape.menu.go-main"
msgstr "Go to main component file"
-#: src/app/main/ui/workspace/context_menu.cljs:272
+#: src/app/main/ui/workspace/context_menu.cljs:273
msgid "workspace.shape.menu.group"
msgstr "Group"
-#: src/app/main/ui/workspace/context_menu.cljs:374, src/app/main/ui/workspace/sidebar/layer_item.cljs:145
+#: src/app/main/ui/workspace/context_menu.cljs:375, src/app/main/ui/workspace/sidebar/layer_item.cljs:145
msgid "workspace.shape.menu.hide"
msgstr "Hide"
-#: src/app/main/ui/workspace/context_menu.cljs:562, src/app/main/ui/workspace/main_menu.cljs:414
+#: src/app/main/ui/workspace/context_menu.cljs:565, src/app/main/ui/workspace/main_menu.cljs:415
msgid "workspace.shape.menu.hide-ui"
msgstr "Show / Hide UI"
-#: src/app/main/ui/workspace/context_menu.cljs:349
+#: src/app/main/ui/workspace/context_menu.cljs:350
msgid "workspace.shape.menu.intersection"
msgstr "Intersection"
-#: src/app/main/ui/workspace/context_menu.cljs:382, src/app/main/ui/workspace/sidebar/layer_item.cljs:153, src/app/main/ui/workspace/sidebar/options/menus/layer.cljs:189
+#: src/app/main/ui/workspace/context_menu.cljs:383, src/app/main/ui/workspace/sidebar/layer_item.cljs:153, src/app/main/ui/workspace/sidebar/options/menus/layer.cljs:189
msgid "workspace.shape.menu.lock"
msgstr "Lock"
-#: src/app/main/ui/workspace/context_menu.cljs:277
+#: src/app/main/ui/workspace/context_menu.cljs:278
msgid "workspace.shape.menu.mask"
msgstr "Mask"
-#: src/app/main/ui/workspace/context_menu.cljs:146, src/app/main/ui/workspace/context_menu.cljs:559
+#: src/app/main/ui/workspace/context_menu.cljs:147, src/app/main/ui/workspace/context_menu.cljs:562
msgid "workspace.shape.menu.paste"
msgstr "Paste"
-#: src/app/main/ui/workspace/context_menu.cljs:342
+#: src/app/main/ui/workspace/context_menu.cljs:343
msgid "workspace.shape.menu.path"
msgstr "Path"
-#: src/app/main/ui/workspace/context_menu.cljs:442
+#: src/app/main/ui/workspace/context_menu.cljs:443
msgid "workspace.shape.menu.remove-flex"
msgstr "Remove flex layout"
-#: src/app/main/ui/workspace/context_menu.cljs:445
+#: src/app/main/ui/workspace/context_menu.cljs:446
msgid "workspace.shape.menu.remove-grid"
msgstr "Remove grid layout"
@@ -6048,59 +6058,59 @@ msgstr "Remove grid layout"
msgid "workspace.shape.menu.remove-layout"
msgstr "Remove layout"
-#: src/app/main/ui/workspace/context_menu.cljs:235
+#: src/app/main/ui/workspace/context_menu.cljs:236
msgid "workspace.shape.menu.rename"
msgstr "Rename"
-#: src/app/main/ui/workspace/sidebar/assets/common.cljs:436
+#: src/app/main/ui/workspace/sidebar/assets/common.cljs:440
msgid "workspace.shape.menu.reset-overrides"
msgstr "Reset overrides"
-#: src/app/main/ui/workspace/sidebar/assets/common.cljs:439
+#: src/app/main/ui/workspace/sidebar/assets/common.cljs:443
msgid "workspace.shape.menu.restore-main"
msgstr "Restore main component"
-#: src/app/main/ui/workspace/context_menu.cljs:175
+#: src/app/main/ui/workspace/context_menu.cljs:176
msgid "workspace.shape.menu.select-layer"
msgstr "Select layer"
-#: src/app/main/ui/workspace/context_menu.cljs:371, src/app/main/ui/workspace/sidebar/layer_item.cljs:144
+#: src/app/main/ui/workspace/context_menu.cljs:372, src/app/main/ui/workspace/sidebar/layer_item.cljs:144
msgid "workspace.shape.menu.show"
msgstr "Show"
-#: src/app/main/ui/workspace/sidebar/assets/common.cljs:424
+#: src/app/main/ui/workspace/sidebar/assets/common.cljs:428
msgid "workspace.shape.menu.show-in-assets"
msgstr "Show in assets panel"
-#: src/app/main/ui/workspace/sidebar/assets/common.cljs:442, src/app/main/ui/workspace/sidebar/assets/components.cljs:585
+#: src/app/main/ui/workspace/sidebar/assets/common.cljs:446, src/app/main/ui/workspace/sidebar/assets/components.cljs:576
msgid "workspace.shape.menu.show-main"
msgstr "Show main component"
-#: src/app/main/ui/workspace/context_menu.cljs:222
+#: src/app/main/ui/workspace/context_menu.cljs:223
msgid "workspace.shape.menu.thumbnail-remove"
msgstr "Remove thumbnail"
-#: src/app/main/ui/workspace/context_menu.cljs:224
+#: src/app/main/ui/workspace/context_menu.cljs:225
msgid "workspace.shape.menu.thumbnail-set"
msgstr "Set as thumbnail"
-#: src/app/main/ui/workspace/context_menu.cljs:337
+#: src/app/main/ui/workspace/context_menu.cljs:338
msgid "workspace.shape.menu.transform-to-path"
msgstr "Transform to path"
-#: src/app/main/ui/workspace/context_menu.cljs:268
+#: src/app/main/ui/workspace/context_menu.cljs:269
msgid "workspace.shape.menu.ungroup"
msgstr "Ungroup"
-#: src/app/main/ui/workspace/context_menu.cljs:343, src/app/main/ui/workspace/sidebar/options/menus/bool.cljs:70
+#: src/app/main/ui/workspace/context_menu.cljs:344, src/app/main/ui/workspace/sidebar/options/menus/bool.cljs:70
msgid "workspace.shape.menu.union"
msgstr "Union"
-#: src/app/main/ui/workspace/context_menu.cljs:379, src/app/main/ui/workspace/sidebar/layer_item.cljs:152, src/app/main/ui/workspace/sidebar/options/menus/layer.cljs:195
+#: src/app/main/ui/workspace/context_menu.cljs:380, src/app/main/ui/workspace/sidebar/layer_item.cljs:152, src/app/main/ui/workspace/sidebar/options/menus/layer.cljs:195
msgid "workspace.shape.menu.unlock"
msgstr "Unlock"
-#: src/app/main/ui/workspace/context_menu.cljs:282
+#: src/app/main/ui/workspace/context_menu.cljs:283
msgid "workspace.shape.menu.unmask"
msgstr "Unmask"
@@ -6109,7 +6119,7 @@ msgstr "Unmask"
msgid "workspace.shape.menu.update-components-in-bulk"
msgstr "Update main components"
-#: src/app/main/ui/workspace/sidebar/assets/common.cljs:445
+#: src/app/main/ui/workspace/sidebar/assets/common.cljs:449
msgid "workspace.shape.menu.update-main"
msgstr "Update main component"
@@ -6125,39 +6135,39 @@ msgstr "Expand sidebar"
msgid "workspace.sidebar.history"
msgstr "History"
-#: src/app/main/ui/workspace/sidebar/layers.cljs:524, src/app/main/ui/workspace/sidebar.cljs:110, src/app/main/ui/workspace/sidebar.cljs:114, src/app/main/ui/workspace/sidebar.cljs:123
+#: src/app/main/ui/workspace/sidebar/layers.cljs:525, src/app/main/ui/workspace/sidebar.cljs:110, src/app/main/ui/workspace/sidebar.cljs:114, src/app/main/ui/workspace/sidebar.cljs:123
msgid "workspace.sidebar.layers"
msgstr "Layers"
-#: src/app/main/ui/workspace/sidebar/layers.cljs:310, src/app/main/ui/workspace/sidebar/layers.cljs:382
+#: src/app/main/ui/workspace/sidebar/layers.cljs:311, src/app/main/ui/workspace/sidebar/layers.cljs:383
msgid "workspace.sidebar.layers.components"
msgstr "Components"
-#: src/app/main/ui/workspace/sidebar/layers.cljs:307, src/app/main/ui/workspace/sidebar/layers.cljs:340
+#: src/app/main/ui/workspace/sidebar/layers.cljs:308, src/app/main/ui/workspace/sidebar/layers.cljs:341
msgid "workspace.sidebar.layers.frames"
msgstr "Boards"
-#: src/app/main/ui/workspace/sidebar/layers.cljs:308, src/app/main/ui/workspace/sidebar/layers.cljs:354
+#: src/app/main/ui/workspace/sidebar/layers.cljs:309, src/app/main/ui/workspace/sidebar/layers.cljs:355
msgid "workspace.sidebar.layers.groups"
msgstr "Groups"
-#: src/app/main/ui/workspace/sidebar/layers.cljs:312, src/app/main/ui/workspace/sidebar/layers.cljs:410
+#: src/app/main/ui/workspace/sidebar/layers.cljs:313, src/app/main/ui/workspace/sidebar/layers.cljs:411
msgid "workspace.sidebar.layers.images"
msgstr "Images"
-#: src/app/main/ui/workspace/sidebar/layers.cljs:309, src/app/main/ui/workspace/sidebar/layers.cljs:368
+#: src/app/main/ui/workspace/sidebar/layers.cljs:310, src/app/main/ui/workspace/sidebar/layers.cljs:369
msgid "workspace.sidebar.layers.masks"
msgstr "Masks"
-#: src/app/main/ui/workspace/sidebar/layers.cljs:290
+#: src/app/main/ui/workspace/sidebar/layers.cljs:291
msgid "workspace.sidebar.layers.search"
msgstr "Search layers"
-#: src/app/main/ui/workspace/sidebar/layers.cljs:313, src/app/main/ui/workspace/sidebar/layers.cljs:424
+#: src/app/main/ui/workspace/sidebar/layers.cljs:314, src/app/main/ui/workspace/sidebar/layers.cljs:425
msgid "workspace.sidebar.layers.shapes"
msgstr "Shapes"
-#: src/app/main/ui/workspace/sidebar/layers.cljs:311, src/app/main/ui/workspace/sidebar/layers.cljs:396
+#: src/app/main/ui/workspace/sidebar/layers.cljs:312, src/app/main/ui/workspace/sidebar/layers.cljs:397
msgid "workspace.sidebar.layers.texts"
msgstr "Texts"
@@ -6304,35 +6314,35 @@ msgstr "Color Palette (%s)"
msgid "workspace.toolbar.comments"
msgstr "Comments (%s)"
-#: src/app/main/ui/workspace/top_toolbar.cljs:181, src/app/main/ui/workspace/top_toolbar.cljs:182
+#: src/app/main/ui/workspace/top_toolbar.cljs:182, src/app/main/ui/workspace/top_toolbar.cljs:183
msgid "workspace.toolbar.curve"
msgstr "Curve (%s)"
-#: src/app/main/ui/workspace/top_toolbar.cljs:161, src/app/main/ui/workspace/top_toolbar.cljs:162
+#: src/app/main/ui/workspace/top_toolbar.cljs:162, src/app/main/ui/workspace/top_toolbar.cljs:163
msgid "workspace.toolbar.ellipse"
msgstr "Ellipse (%s)"
-#: src/app/main/ui/workspace/top_toolbar.cljs:143, src/app/main/ui/workspace/top_toolbar.cljs:144
+#: src/app/main/ui/workspace/top_toolbar.cljs:144, src/app/main/ui/workspace/top_toolbar.cljs:145
msgid "workspace.toolbar.frame"
msgstr "Board (%s)"
-#: src/app/main/ui/workspace/top_toolbar.cljs:60, src/app/main/ui/workspace/top_toolbar.cljs:61
+#: src/app/main/ui/workspace/top_toolbar.cljs:61, src/app/main/ui/workspace/top_toolbar.cljs:62
msgid "workspace.toolbar.image"
msgstr "Image (%s)"
-#: src/app/main/ui/workspace/top_toolbar.cljs:133, src/app/main/ui/workspace/top_toolbar.cljs:134
+#: src/app/main/ui/workspace/top_toolbar.cljs:134, src/app/main/ui/workspace/top_toolbar.cljs:135
msgid "workspace.toolbar.move"
msgstr "Move (%s)"
-#: src/app/main/ui/workspace/top_toolbar.cljs:190, src/app/main/ui/workspace/top_toolbar.cljs:191
+#: src/app/main/ui/workspace/top_toolbar.cljs:191, src/app/main/ui/workspace/top_toolbar.cljs:192
msgid "workspace.toolbar.path"
msgstr "Path (%s)"
-#: src/app/main/ui/workspace/top_toolbar.cljs:201, src/app/main/ui/workspace/top_toolbar.cljs:202
+#: src/app/main/ui/workspace/top_toolbar.cljs:202, src/app/main/ui/workspace/top_toolbar.cljs:203
msgid "workspace.toolbar.plugins"
msgstr "Plugins (%s)"
-#: src/app/main/ui/workspace/top_toolbar.cljs:152, src/app/main/ui/workspace/top_toolbar.cljs:153
+#: src/app/main/ui/workspace/top_toolbar.cljs:153, src/app/main/ui/workspace/top_toolbar.cljs:154
msgid "workspace.toolbar.rect"
msgstr "Rectangle (%s)"
@@ -6341,7 +6351,7 @@ msgstr "Rectangle (%s)"
msgid "workspace.toolbar.shortcuts"
msgstr "Shortcuts (%s)"
-#: src/app/main/ui/workspace/top_toolbar.cljs:170, src/app/main/ui/workspace/top_toolbar.cljs:171
+#: src/app/main/ui/workspace/top_toolbar.cljs:171, src/app/main/ui/workspace/top_toolbar.cljs:172
msgid "workspace.toolbar.text"
msgstr "Text (%s)"
@@ -6349,7 +6359,7 @@ msgstr "Text (%s)"
msgid "workspace.toolbar.text-palette"
msgstr "Typographies (%s)"
-#: src/app/main/ui/workspace/top_toolbar.cljs:219, src/app/main/ui/workspace/top_toolbar.cljs:220
+#: src/app/main/ui/workspace/top_toolbar.cljs:220, src/app/main/ui/workspace/top_toolbar.cljs:221
msgid "workspace.toolbar.toggle-toolbar"
msgstr "Toggle toolbar"
@@ -6362,143 +6372,143 @@ msgstr "Done"
msgid "workspace.top-bar.view-only"
msgstr "**Inspecting code** (View Only)"
-#: src/app/main/ui/workspace/sidebar/history.cljs:331
+#: src/app/main/ui/workspace/sidebar/history.cljs:332
msgid "workspace.undo.empty"
msgstr "There are no history changes so far"
-#: src/app/main/ui/workspace/sidebar/history.cljs:145
+#: src/app/main/ui/workspace/sidebar/history.cljs:146
msgid "workspace.undo.entry.delete"
msgstr "Deleted %s"
-#: src/app/main/ui/workspace/sidebar/history.cljs:144
+#: src/app/main/ui/workspace/sidebar/history.cljs:145
msgid "workspace.undo.entry.modify"
msgstr "Modified %s"
-#: src/app/main/ui/workspace/sidebar/history.cljs:146
+#: src/app/main/ui/workspace/sidebar/history.cljs:147
msgid "workspace.undo.entry.move"
msgstr "Moved objects"
-#: src/app/main/ui/workspace/sidebar/history.cljs:109
+#: src/app/main/ui/workspace/sidebar/history.cljs:110
msgid "workspace.undo.entry.multiple.circle"
msgstr "circles"
-#: src/app/main/ui/workspace/sidebar/history.cljs:110
+#: src/app/main/ui/workspace/sidebar/history.cljs:111
msgid "workspace.undo.entry.multiple.color"
msgstr "color assets"
-#: src/app/main/ui/workspace/sidebar/history.cljs:111
+#: src/app/main/ui/workspace/sidebar/history.cljs:112
msgid "workspace.undo.entry.multiple.component"
msgstr "components"
-#: src/app/main/ui/workspace/sidebar/history.cljs:112
+#: src/app/main/ui/workspace/sidebar/history.cljs:113
msgid "workspace.undo.entry.multiple.curve"
msgstr "curves"
-#: src/app/main/ui/workspace/sidebar/history.cljs:113
+#: src/app/main/ui/workspace/sidebar/history.cljs:114
msgid "workspace.undo.entry.multiple.frame"
msgstr "board"
-#: src/app/main/ui/workspace/sidebar/history.cljs:114
+#: src/app/main/ui/workspace/sidebar/history.cljs:115
msgid "workspace.undo.entry.multiple.group"
msgstr "groups"
-#: src/app/main/ui/workspace/sidebar/history.cljs:115
+#: src/app/main/ui/workspace/sidebar/history.cljs:116
msgid "workspace.undo.entry.multiple.media"
msgstr "graphic assets"
-#: src/app/main/ui/workspace/sidebar/history.cljs:116
+#: src/app/main/ui/workspace/sidebar/history.cljs:117
msgid "workspace.undo.entry.multiple.multiple"
msgstr "objects"
-#: src/app/main/ui/workspace/sidebar/history.cljs:117
+#: src/app/main/ui/workspace/sidebar/history.cljs:118
msgid "workspace.undo.entry.multiple.page"
msgstr "pages"
-#: src/app/main/ui/workspace/sidebar/history.cljs:118
+#: src/app/main/ui/workspace/sidebar/history.cljs:119
msgid "workspace.undo.entry.multiple.path"
msgstr "paths"
-#: src/app/main/ui/workspace/sidebar/history.cljs:119
+#: src/app/main/ui/workspace/sidebar/history.cljs:120
msgid "workspace.undo.entry.multiple.rect"
msgstr "rectangles"
-#: src/app/main/ui/workspace/sidebar/history.cljs:120
+#: src/app/main/ui/workspace/sidebar/history.cljs:121
msgid "workspace.undo.entry.multiple.shape"
msgstr "shapes"
-#: src/app/main/ui/workspace/sidebar/history.cljs:121
+#: src/app/main/ui/workspace/sidebar/history.cljs:122
msgid "workspace.undo.entry.multiple.text"
msgstr "texts"
-#: src/app/main/ui/workspace/sidebar/history.cljs:122
+#: src/app/main/ui/workspace/sidebar/history.cljs:123
msgid "workspace.undo.entry.multiple.typography"
msgstr "typography assets"
-#: src/app/main/ui/workspace/sidebar/history.cljs:143
+#: src/app/main/ui/workspace/sidebar/history.cljs:144
msgid "workspace.undo.entry.new"
msgstr "New %s"
-#: src/app/main/ui/workspace/sidebar/history.cljs:123
+#: src/app/main/ui/workspace/sidebar/history.cljs:124
msgid "workspace.undo.entry.single.circle"
msgstr "circle"
-#: src/app/main/ui/workspace/sidebar/history.cljs:124
+#: src/app/main/ui/workspace/sidebar/history.cljs:125
msgid "workspace.undo.entry.single.color"
msgstr "color asset"
-#: src/app/main/ui/workspace/sidebar/history.cljs:125
+#: src/app/main/ui/workspace/sidebar/history.cljs:126
msgid "workspace.undo.entry.single.component"
msgstr "component"
-#: src/app/main/ui/workspace/sidebar/history.cljs:126
+#: src/app/main/ui/workspace/sidebar/history.cljs:127
msgid "workspace.undo.entry.single.curve"
msgstr "curve"
-#: src/app/main/ui/workspace/sidebar/history.cljs:127
+#: src/app/main/ui/workspace/sidebar/history.cljs:128
msgid "workspace.undo.entry.single.frame"
msgstr "board"
-#: src/app/main/ui/workspace/sidebar/history.cljs:128
+#: src/app/main/ui/workspace/sidebar/history.cljs:129
msgid "workspace.undo.entry.single.group"
msgstr "group"
-#: src/app/main/ui/workspace/sidebar/history.cljs:129
+#: src/app/main/ui/workspace/sidebar/history.cljs:130
msgid "workspace.undo.entry.single.image"
msgstr "image"
-#: src/app/main/ui/workspace/sidebar/history.cljs:130
+#: src/app/main/ui/workspace/sidebar/history.cljs:131
msgid "workspace.undo.entry.single.media"
msgstr "graphic asset"
-#: src/app/main/ui/workspace/sidebar/history.cljs:131
+#: src/app/main/ui/workspace/sidebar/history.cljs:132
msgid "workspace.undo.entry.single.multiple"
msgstr "object"
-#: src/app/main/ui/workspace/sidebar/history.cljs:132
+#: src/app/main/ui/workspace/sidebar/history.cljs:133
msgid "workspace.undo.entry.single.page"
msgstr "page"
-#: src/app/main/ui/workspace/sidebar/history.cljs:133
+#: src/app/main/ui/workspace/sidebar/history.cljs:134
msgid "workspace.undo.entry.single.path"
msgstr "path"
-#: src/app/main/ui/workspace/sidebar/history.cljs:134
+#: src/app/main/ui/workspace/sidebar/history.cljs:135
msgid "workspace.undo.entry.single.rect"
msgstr "rectangle"
-#: src/app/main/ui/workspace/sidebar/history.cljs:135
+#: src/app/main/ui/workspace/sidebar/history.cljs:136
msgid "workspace.undo.entry.single.shape"
msgstr "shape"
-#: src/app/main/ui/workspace/sidebar/history.cljs:136
+#: src/app/main/ui/workspace/sidebar/history.cljs:137
msgid "workspace.undo.entry.single.text"
msgstr "text"
-#: src/app/main/ui/workspace/sidebar/history.cljs:137
+#: src/app/main/ui/workspace/sidebar/history.cljs:138
msgid "workspace.undo.entry.single.typography"
msgstr "typography asset"
-#: src/app/main/ui/workspace/sidebar/history.cljs:147
+#: src/app/main/ui/workspace/sidebar/history.cljs:148
msgid "workspace.undo.entry.unknown"
msgstr "Operation over %s"
@@ -6507,95 +6517,101 @@ msgstr "Operation over %s"
msgid "workspace.undo.title"
msgstr "History"
-#: src/app/main/data/workspace/libraries.cljs:1147, src/app/main/ui/workspace/sidebar/versions.cljs:285
+#: src/app/main/data/workspace/libraries.cljs:1178, src/app/main/ui/workspace/sidebar/versions.cljs:289
msgid "workspace.updates.dismiss"
msgstr "Dismiss"
-#: src/app/main/data/workspace/libraries.cljs:1145
+#: src/app/main/data/workspace/libraries.cljs:1176
msgid "workspace.updates.more-info"
msgstr "More info"
-#: src/app/main/data/workspace/libraries.cljs:1143
+#: src/app/main/data/workspace/libraries.cljs:1174
msgid "workspace.updates.there-are-updates"
msgstr "There are updates in shared libraries"
-#: src/app/main/data/workspace/libraries.cljs:1150
+#: src/app/main/data/workspace/libraries.cljs:1181
msgid "workspace.updates.update"
msgstr "Update"
-#: src/app/main/ui/workspace/sidebar/versions.cljs:193
+#: src/app/main/ui/workspace/sidebar/versions.cljs:196
msgid "workspace.versions.autosaved.entry"
msgstr "%s autosave versions"
-#: src/app/main/ui/workspace/sidebar/versions.cljs:187
+#: src/app/main/ui/workspace/sidebar/versions.cljs:190
msgid "workspace.versions.autosaved.version"
msgstr "Autosaved %s"
-#: src/app/main/ui/workspace/sidebar/versions.cljs:224
+#: src/app/main/ui/workspace/sidebar/versions.cljs:227
msgid "workspace.versions.button.pin"
msgstr "Pin version"
-#: src/app/main/ui/workspace/sidebar/versions.cljs:219
+#: src/app/main/ui/workspace/sidebar/versions.cljs:222
msgid "workspace.versions.button.restore"
msgstr "Restore version"
-#: src/app/main/ui/workspace/sidebar/versions.cljs:345, src/app/main/ui/workspace/sidebar/versions.cljs:347
+#: src/app/main/ui/workspace/sidebar/versions.cljs:361, src/app/main/ui/workspace/sidebar/versions.cljs:363
msgid "workspace.versions.button.save"
msgstr "Save version"
-#: src/app/main/ui/workspace/sidebar/versions.cljs:354
+#: src/app/main/ui/workspace/sidebar/versions.cljs:370
msgid "workspace.versions.empty"
msgstr "There are no versions yet"
-#: src/app/main/ui/workspace/sidebar/versions.cljs:190
+#: src/app/main/ui/workspace/sidebar/versions.cljs:193
msgid "workspace.versions.expand-snapshot"
msgstr "Expand snapshots"
-#: src/app/main/ui/workspace/sidebar/versions.cljs:327
+#: src/app/main/ui/workspace/sidebar/versions.cljs:343
msgid "workspace.versions.filter.all"
msgstr "All versions"
-#: src/app/main/ui/workspace/sidebar/versions.cljs:326
+#: src/app/main/ui/workspace/sidebar/versions.cljs:342
msgid "workspace.versions.filter.label"
msgstr "Versions filter"
-#: src/app/main/ui/workspace/sidebar/versions.cljs:328
+#: src/app/main/ui/workspace/sidebar/versions.cljs:344
msgid "workspace.versions.filter.mine"
msgstr "My versions"
-#: src/app/main/ui/workspace/sidebar/versions.cljs:334
+#: src/app/main/ui/workspace/sidebar/versions.cljs:350
msgid "workspace.versions.filter.user"
msgstr "%s's versions"
-#: src/app/main/ui/workspace/sidebar/versions.cljs:340
+#: src/app/main/ui/workspace/sidebar/versions.cljs:356
msgid "workspace.versions.loading"
msgstr "Loading..."
-#: src/app/main/ui/workspace/sidebar/versions.cljs:283
+#: src/app/main/ui/workspace/sidebar/versions.cljs:287
msgid "workspace.versions.restore-warning"
msgstr "Do you want to restore this version?"
-msgid "workspace.versions.warning.text"
-msgstr "Autosaved versions will be kept for %s days."
-
-#, markdown
-msgid "workspace.versions.warning.subtext"
-msgstr "If you'd like to increase this limit, write to us at [support@penpot.app](%s)"
-
-#: src/app/main/ui/workspace/sidebar/versions.cljs:207
+#: src/app/main/ui/workspace/sidebar/versions.cljs:210
msgid "workspace.versions.snapshot-menu"
msgstr "Open snapshot menu"
-#: src/app/main/ui/workspace/sidebar/versions.cljs:138
+#: src/app/main/ui/workspace/sidebar.cljs:242
+msgid "workspace.versions.tab.actions"
+msgstr "Actions"
+
+#: src/app/main/ui/workspace/sidebar.cljs:241
+msgid "workspace.versions.tab.history"
+msgstr "History"
+
+#: src/app/main/ui/workspace/sidebar/versions.cljs:141
msgid "workspace.versions.version-menu"
msgstr "Open version menu"
+#: src/app/main/ui/workspace/sidebar/versions.cljs:402
+#, markdown
+msgid "workspace.versions.warning.subtext"
+msgstr ""
+"If you'd like to increase this limit, write to us at "
+"[support@penpot.app](%s)"
+
+#: src/app/main/ui/workspace/sidebar/versions.cljs:397
+msgid "workspace.versions.warning.text"
+msgstr "Autosaved versions will be kept for %s days."
+
#, unused
msgid "workspace.viewport.click-to-close-path"
msgstr "Click to close the path"
-
-msgid "workspace.versions.tab.history"
-msgstr "History"
-
-msgid "workspace.versions.tab.actions"
-msgstr "Actions"
diff --git a/frontend/translations/es.po b/frontend/translations/es.po
index 23691241e2..e1a05a95f9 100644
--- a/frontend/translations/es.po
+++ b/frontend/translations/es.po
@@ -394,6 +394,10 @@ msgstr "El token expirará el %s"
msgid "dashboard.access-tokens.token-will-not-expire"
msgstr "El token no tiene fecha de expiración"
+#: src/app/main/ui/dashboard/placeholder.cljs:48
+msgid "dashboard.add-file"
+msgstr "Añadir archivo"
+
#: src/app/main/ui/dashboard/file_menu.cljs:311, src/app/main/ui/workspace/main_menu.cljs:585
msgid "dashboard.add-shared"
msgstr "Añadir como Biblioteca Compartida"
From f553fa10d841757fc040d48e68a8e96d679cbb6b Mon Sep 17 00:00:00 2001
From: "alonso.torres"
Date: Fri, 17 Jan 2025 15:34:21 +0100
Subject: [PATCH 17/26] :bug: Fix problem in plugins with `replaceColor` method
---
CHANGES.md | 1 +
frontend/src/app/plugins/api.cljs | 8 ++++---
frontend/src/app/plugins/format.cljs | 23 ++++++++++----------
frontend/src/app/plugins/library.cljs | 1 +
frontend/src/app/plugins/parser.cljs | 30 +++++++++++++++++----------
5 files changed, 38 insertions(+), 25 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index a138453cc2..e8020a4ed2 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -5,6 +5,7 @@
### :bug: Bugs fixed
- Fix detach when top copy is dangling and nested copy is not [Taiga #9699](https://tree.taiga.io/project/penpot/issue/9699)
+- Fix problem in plugins with `replaceColor` method [#174](https://github.com/penpot/penpot-plugins/issues/174)
## 2.4.1
diff --git a/frontend/src/app/plugins/api.cljs b/frontend/src/app/plugins/api.cljs
index 7bc0fd1fc5..8516ca3c69 100644
--- a/frontend/src/app/plugins/api.cljs
+++ b/frontend/src/app/plugins/api.cljs
@@ -166,8 +166,8 @@
:replaceColor
(fn [shapes old-color new-color]
- (let [old-color (parser/parse-color old-color)
- new-color (parser/parse-color new-color)]
+ (let [old-color (parser/parse-color-data old-color)
+ new-color (parser/parse-color-data new-color)]
(cond
(or (not (array? shapes)) (not (every? shape/shape-proxy? shapes)))
(u/display-not-valid :replaceColor-shapes shapes)
@@ -190,7 +190,9 @@
shapes-by-color
(->> (ctc/extract-all-colors shapes file-id shared-libs)
(group-by :attrs))]
- (st/emit! (dwc/change-color-in-selected new-color (get shapes-by-color old-color) old-color))))))
+
+ (when-let [operations (get shapes-by-color old-color)]
+ (st/emit! (dwc/change-color-in-selected operations new-color old-color)))))))
:getRoot
(fn []
diff --git a/frontend/src/app/plugins/format.cljs b/frontend/src/app/plugins/format.cljs
index b39f422bc9..829476da0b 100644
--- a/frontend/src/app/plugins/format.cljs
+++ b/frontend/src/app/plugins/format.cljs
@@ -128,18 +128,19 @@
;; image?: ImageData;
;; }
(defn format-color
- [{:keys [id name path color opacity ref-id ref-file gradient image] :as color-data}]
+ [{:keys [id file-id name path color opacity ref-id ref-file gradient image] :as color-data}]
(when (some? color-data)
- (obj/without-empty
- #js {:id (format-id id)
- :name name
- :path path
- :color color
- :opacity opacity
- :refId (format-id ref-id)
- :refFile (format-id ref-file)
- :gradient (format-gradient gradient)
- :image (format-image image)})))
+ (let [id (or (format-id id) (format-id ref-id))
+ file-id (or (format-id file-id) (format-id ref-file))]
+ (obj/without-empty
+ #js {:id (or (format-id id) (format-id ref-id))
+ :fileId (or (format-id file-id) (format-id ref-file))
+ :name name
+ :path path
+ :color color
+ :opacity opacity
+ :gradient (format-gradient gradient)
+ :image (format-image image)}))))
;; Color & ColorShapeInfo
(defn format-color-result
diff --git a/frontend/src/app/plugins/library.cljs b/frontend/src/app/plugins/library.cljs
index 31241a4bf7..d1dc2096f1 100644
--- a/frontend/src/app/plugins/library.cljs
+++ b/frontend/src/app/plugins/library.cljs
@@ -48,6 +48,7 @@
:$file {:enumerable false :get (constantly file-id)}
:id {:get (fn [] (dm/str id))}
+ :fileId {:get #(dm/str file-id)}
:name
{:this true
diff --git a/frontend/src/app/plugins/parser.cljs b/frontend/src/app/plugins/parser.cljs
index 4731528abe..8d884350dd 100644
--- a/frontend/src/app/plugins/parser.cljs
+++ b/frontend/src/app/plugins/parser.cljs
@@ -111,28 +111,36 @@
;; export interface Color {
;; id?: string;
+;; fileId?: string;
+;; refId?: string; // deprecated
+;; refFile?: string; // deprecated
;; name?: string;
;; path?: string;
;; color?: string;
;; opacity?: number;
-;; refId?: string;
-;; refFile?: string;
;; gradient?: Gradient;
;; image?: ImageData;
;; }
+(defn parse-color-data
+ [^js color]
+ (when (some? color)
+ (let [id (or (obj/get color "id") (obj/get color "refId"))
+ file-id (or (obj/get color "fileId") (obj/get color "refFile"))]
+ (d/without-nils
+ {:id (parse-id id)
+ :file-id (parse-id file-id)
+ :color (-> (obj/get color "color") parse-hex)
+ :opacity (obj/get color "opacity")
+ :gradient (-> (obj/get color "gradient") parse-gradient)
+ :image (-> (obj/get color "image") parse-image-data)}))))
+
(defn parse-color
[^js color]
(when (some? color)
(d/without-nils
- {:id (-> (obj/get color "id") parse-id)
- :name (obj/get color "name")
- :path (obj/get color "path")
- :color (-> (obj/get color "color") parse-hex)
- :opacity (obj/get color "opacity")
- :ref-id (-> (obj/get color "refId") parse-id)
- :ref-file (-> (obj/get color "refFile") parse-id)
- :gradient (-> (obj/get color "gradient") parse-gradient)
- :image (-> (obj/get color "image") parse-image-data)})))
+ (-> (parse-color-data color)
+ (assoc :name (obj/get color "name")
+ :path (obj/get color "path"))))))
;; export interface Shadow {
;; id?: string;
From 667b5fb6eecba9167b590691140cdfe653188045 Mon Sep 17 00:00:00 2001
From: Andrey Antukh
Date: Mon, 20 Jan 2025 12:30:20 +0100
Subject: [PATCH 18/26] :bug: Fix missing methods reference from api docs page
---
CHANGES.md | 2 ++
backend/src/app/rpc/doc.clj | 3 ++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/CHANGES.md b/CHANGES.md
index e8020a4ed2..b3fc1a750f 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -6,6 +6,8 @@
- Fix detach when top copy is dangling and nested copy is not [Taiga #9699](https://tree.taiga.io/project/penpot/issue/9699)
- Fix problem in plugins with `replaceColor` method [#174](https://github.com/penpot/penpot-plugins/issues/174)
+- Fix missing methods reference on API Docs
+
## 2.4.1
diff --git a/backend/src/app/rpc/doc.clj b/backend/src/app/rpc/doc.clj
index 217e86332d..efd23ed44a 100644
--- a/backend/src/app/rpc/doc.clj
+++ b/backend/src/app/rpc/doc.clj
@@ -87,6 +87,7 @@
(let [params (:query-params request)
pstyle (:type params "js")
context (assoc context :param-style pstyle)]
+
{::yres/status 200
::yres/body (-> (io/resource "app/templates/api-doc.tmpl")
(tmpl/render context))}))
@@ -207,7 +208,7 @@
(assert (sm/valid? ::rpc/methods (::rpc/methods params)) "expected valid methods"))
(defmethod ig/init-key ::routes
- [_ {:keys [methods] :as cfg}]
+ [_ {:keys [::rpc/methods] :as cfg}]
[(let [context (prepare-doc-context methods)]
[["/_doc"
{:handler (doc-handler context)
From 089a66881c5816c37a42d405e781a6a327531943 Mon Sep 17 00:00:00 2001
From: Andrey Antukh
Date: Mon, 20 Jan 2025 12:30:30 +0100
Subject: [PATCH 19/26] :sparkles: Make frontend app setup logging message more
easy to be read
Mainly printing flag per line, making it more easily for human eye look
if some feature is active or not
---
frontend/src/app/main.cljs | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs
index e9824671f1..57929dfe5d 100644
--- a/frontend/src/app/main.cljs
+++ b/frontend/src/app/main.cljs
@@ -30,20 +30,21 @@
[app.util.i18n :as i18n]
[app.util.theme :as theme]
[beicon.v2.core :as rx]
- [cuerdas.core :as str]
[debug]
[features]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(log/setup! {:app :info})
+(log/set-level! :debug)
(when (= :browser cf/target)
- (log/info :version (:full cf/version)
- :asserts *assert*
- :build-date cf/build-date
- :public-uri (dm/str cf/public-uri))
- (log/info :flags (str/join "," (map name cf/flags))))
+ (log/inf :version (:full cf/version)
+ :asserts *assert*
+ :build-date cf/build-date
+ :public-uri (dm/str cf/public-uri))
+ (doseq [flag cf/flags]
+ (log/dbg :hint "flag enabled" :flag (name flag))))
(declare reinit)
From fde0f3c182aee4ae638c3476f78a9b8610b55723 Mon Sep 17 00:00:00 2001
From: Andrey Antukh
Date: Wed, 15 Jan 2025 12:19:14 +0100
Subject: [PATCH 20/26] :bug: Pass correct default options on db/plan fn
---
backend/src/app/db.clj | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj
index d02a8ee4ed..b22ecd899d 100644
--- a/backend/src/app/db.clj
+++ b/backend/src/app/db.clj
@@ -414,9 +414,12 @@
row))
(defn plan
- [ds sql]
- (-> (get-connectable ds)
- (jdbc/plan sql sql/default-opts)))
+ ([ds sql]
+ (-> (get-connectable ds)
+ (jdbc/plan sql default-opts)))
+ ([ds sql opts]
+ (-> (get-connectable ds)
+ (jdbc/plan sql (merge default-opts opts)))))
(defn cursor
"Return a lazy seq of rows using server side cursors"
From 5513daf17d1ee72bafae7d9fd7c17b7b743fd06f Mon Sep 17 00:00:00 2001
From: Andrey Antukh
Date: Wed, 15 Jan 2025 12:36:13 +0100
Subject: [PATCH 21/26] :recycle: Make the media cleaning mechanism on file-gc
task more efficient
Replaces the use of db/cursor with db/plan, that teorethically allows
processing large results without consuming all result set in memory
---
backend/src/app/tasks/file_gc.clj | 23 +++++---
backend/test/backend_tests/helpers.clj | 2 +-
backend/test/backend_tests/rpc_file_test.clj | 56 +++++++++-----------
3 files changed, 44 insertions(+), 37 deletions(-)
diff --git a/backend/src/app/tasks/file_gc.clj b/backend/src/app/tasks/file_gc.clj
index 7e3c3ee27d..facc7b60fc 100644
--- a/backend/src/app/tasks/file_gc.clj
+++ b/backend/src/app/tasks/file_gc.clj
@@ -53,16 +53,27 @@
RETURNING id")
(def ^:private xf:collect-used-media
- (comp (map :data) (mapcat bfc/collect-used-media)))
+ (comp
+ (map :data)
+ (mapcat bfc/collect-used-media)))
+
+(def ^:private plan-opts
+ {:fetch-size 1
+ :concurrency :read-only
+ :cursors :close
+ :result-type :forward-only})
(defn- clean-file-media!
"Performs the garbage collection of file media objects."
[{:keys [::db/conn] :as cfg} {:keys [id] :as file}]
- (let [used (into #{}
- xf:collect-used-media
- (cons file
- (->> (db/cursor conn [sql:get-snapshots id])
- (map (partial decode-file cfg)))))
+ (let [xform (comp
+ (map (partial decode-file cfg))
+ xf:collect-used-media)
+
+ used (->> (db/plan conn [sql:get-snapshots id] plan-opts)
+ (transduce xform conj #{}))
+ used (into used xf:collect-used-media [file])
+
ids (db/create-array conn "uuid" used)
unused (->> (db/exec! conn [sql:mark-file-media-object-deleted id ids])
(into #{} (map :id)))]
diff --git a/backend/test/backend_tests/helpers.clj b/backend/test/backend_tests/helpers.clj
index 3aa7d1589c..1e1fb7abfb 100644
--- a/backend/test/backend_tests/helpers.clj
+++ b/backend/test/backend_tests/helpers.clj
@@ -62,7 +62,7 @@
(def default
{:database-uri "postgresql://postgres/penpot_test"
:redis-uri "redis://redis/1"
- :file-snapshot-every 1})
+ :auto-file-snapshot-every 1})
(def config
(cf/read-config :prefix "penpot-test"
diff --git a/backend/test/backend_tests/rpc_file_test.clj b/backend/test/backend_tests/rpc_file_test.clj
index 679d5221e0..733c4a1fd7 100644
--- a/backend/test/backend_tests/rpc_file_test.clj
+++ b/backend/test/backend_tests/rpc_file_test.clj
@@ -383,8 +383,19 @@
;; as deleted.
(t/is (true? (th/run-task! :file-gc {:min-age 0 :file-id (:id file)})))
+ ;; This only clears fragments, the file media objects still referenced because
+ ;; snapshots are preserved
(let [res (th/run-task! :objects-gc {:min-age 0})]
- (t/is (= 3 (:processed res))))
+ (t/is (= 2 (:processed res))))
+
+ ;; Mark all snapshots to be a non-snapshot file change
+ (th/db-exec! ["update file_change set data = null where file_id = ?" (:id file)])
+ (th/db-exec! ["update file set has_media_trimmed = false where id = ?" (:id file)])
+
+ ;; Rerun the file-gc and objects-gc
+ (t/is (true? (th/run-task! :file-gc {:min-age 0 :file-id (:id file)})))
+ (let [res (th/run-task! :objects-gc {:min-age 0})]
+ (t/is (= 2 (:processed res))))
;; Now that file-gc have deleted the file-media-object usage,
;; lets execute the touched-gc task, we should see that two of
@@ -417,20 +428,6 @@
;; (th/print-result! out)
(t/is (nil? (:error out)))
- (:result out)))
-
- (update-file! [& {:keys [profile-id file-id changes revn] :or {revn 0}}]
- (let [params {::th/type :update-file
- ::rpc/profile-id profile-id
- :id file-id
- :session-id (uuid/random)
- :revn revn
- :vern 0
- :components-v2 true
- :changes changes}
- out (th/command! params)]
- ;; (th/print-result! out)
- (t/is (nil? (:error out)))
(:result out)))]
(let [storage (:app.storage/storage th/*system*)
@@ -550,8 +547,20 @@
;; as deleted.
(t/is (true? (th/run-task! :file-gc {:min-age 0 :file-id (:id file)})))
+ ;; This only removes unused fragments, file media are still
+ ;; referenced on snapshots.
(let [res (th/run-task! :objects-gc {:min-age 0})]
- (t/is (= 7 (:processed res))))
+ (t/is (= 2 (:processed res))))
+
+ ;; Mark all snapshots to be a non-snapshot file change
+ (th/db-exec! ["update file set has_media_trimmed = false where id = ?" (:id file)])
+ (th/db-exec! ["update file_change set data = null where file_id = ?" (:id file)])
+
+ ;; Rerun file-gc and objects-gc task for the same file once all snapshots are
+ ;; "expired/deleted"
+ (t/is (true? (th/run-task! :file-gc {:min-age 0 :file-id (:id file)})))
+ (let [res (th/run-task! :objects-gc {:min-age 0})]
+ (t/is (= 6 (:processed res))))
(let [rows (th/db-query :file-data-fragment {:file-id (:id file)
:deleted-at nil})]
@@ -591,20 +600,7 @@
;; (th/print-result! out)
(t/is (nil? (:error out)))
- (:result out)))
-
- #_(update-file! [& {:keys [profile-id file-id changes revn] :or {revn 0}}]
- (let [params {::th/type :update-file
- ::rpc/profile-id profile-id
- :id file-id
- :session-id (uuid/random)
- :revn revn
- :features cfeat/supported-features
- :changes changes}
- out (th/command! params)]
- ;; (th/print-result! out)
- (t/is (nil? (:error out)))
- (:result out)))]
+ (:result out)))]
(let [storage (:app.storage/storage th/*system*)
profile (th/create-profile* 1)
From 151aedcf9115ea3cc8f66762e40719e8656c15b8 Mon Sep 17 00:00:00 2001
From: Andrey Antukh
Date: Mon, 20 Jan 2025 16:35:14 +0100
Subject: [PATCH 22/26] :recycle: Make the components cleaning mechanism on
file-gc task more efficient
---
backend/src/app/tasks/file_gc.clj | 67 ++--
backend/test/backend_tests/rpc_file_test.clj | 326 +++++++++++++++++++
2 files changed, 357 insertions(+), 36 deletions(-)
diff --git a/backend/src/app/tasks/file_gc.clj b/backend/src/app/tasks/file_gc.clj
index facc7b60fc..7bbdfa07e1 100644
--- a/backend/src/app/tasks/file_gc.clj
+++ b/backend/src/app/tasks/file_gc.clj
@@ -26,7 +26,6 @@
[app.util.pointer-map :as pmap]
[app.util.time :as dt]
[app.worker :as wrk]
- [clojure.set :as set]
[integrant.core :as ig]))
(declare ^:private get-file)
@@ -156,51 +155,47 @@
AND f.deleted_at IS null
ORDER BY f.modified_at ASC")
+(def ^:private xf:map-id (map :id))
+
+(defn- get-used-components
+ "Given a file and a set of components marked for deletion, return a
+ filtered set of component ids that are still un use"
+ [components library-id {:keys [data]}]
+ (filter #(ctf/used-in? data library-id % :component) components))
+
(defn- clean-deleted-components!
"Performs the garbage collection of unreferenced deleted components."
[{:keys [::db/conn] :as cfg} {:keys [data] :as file}]
(let [file-id (:id file)
- get-used-components
- (fn [data components]
- ;; Find which of the components are used in the file.
- (into #{}
- (filter #(ctf/used-in? data file-id % :component))
- components))
+ deleted-components
+ (ctkl/deleted-components-seq data)
- get-unused-components
- (fn [components files]
- ;; Find and return a set of unused components (on all files).
- (reduce (fn [components {:keys [data]}]
- (if (seq components)
- (->> (get-used-components data components)
- (set/difference components))
- (reduced components)))
+ xform
+ (mapcat (partial get-used-components deleted-components file-id))
- components
- files))
+ used-remote
+ (->> (db/plan conn [sql:get-files-for-library file-id] plan-opts)
+ (transduce (comp (map (partial decode-file cfg)) xform) conj #{}))
- process-fdata
- (fn [data unused]
- (reduce (fn [data id]
- (l/trc :hint "delete component"
- :component-id (str id)
- :file-id (str file-id))
- (ctkl/delete-component data id))
- data
- unused))
+ used-local
+ (into #{} xform [file])
- deleted (into #{} (ctkl/deleted-components-seq data))
-
- unused (->> (db/cursor conn [sql:get-files-for-library file-id] {:chunk-size 1})
- (map (partial decode-file cfg))
- (cons file)
- (get-unused-components deleted)
- (mapv :id)
- (set))
-
- file (update file :data process-fdata unused)]
+ unused
+ (transduce xf:map-id disj
+ (into #{} xf:map-id deleted-components)
+ (concat used-remote used-local))
+ file
+ (update file :data
+ (fn [data]
+ (reduce (fn [data id]
+ (l/trc :hint "delete component"
+ :component-id (str id)
+ :file-id (str file-id))
+ (ctkl/delete-component data id))
+ data
+ unused)))]
(l/dbg :hint "clean" :rel "components" :file-id (str file-id) :total (count unused))
file))
diff --git a/backend/test/backend_tests/rpc_file_test.clj b/backend/test/backend_tests/rpc_file_test.clj
index 733c4a1fd7..b95358101b 100644
--- a/backend/test/backend_tests/rpc_file_test.clj
+++ b/backend/test/backend_tests/rpc_file_test.clj
@@ -1332,3 +1332,329 @@
(t/is (every? #(bytes? (:data %)) rows))
(t/is (every? #(nil? (:data-ref-id %)) rows))
(t/is (every? #(nil? (:data-backend %)) rows)))))
+
+(t/deftest file-gc-with-components-1
+ (let [storage (:app.storage/storage th/*system*)
+ profile (th/create-profile* 1)
+ file (th/create-file* 1 {:profile-id (:id profile)
+ :project-id (:default-project-id profile)
+ :is-shared false})
+
+ s-id-1 (uuid/random)
+ s-id-2 (uuid/random)
+ c-id (uuid/random)
+
+ page-id (first (get-in file [:data :pages]))]
+
+ (let [rows (th/db-query :file-data-fragment {:file-id (:id file)
+ :deleted-at nil})]
+ (t/is (= (count rows) 1)))
+
+ ;; Update file inserting new component
+ (update-file!
+ :file-id (:id file)
+ :profile-id (:id profile)
+ :revn 0
+ :vern 0
+ :changes
+ [{:type :add-obj
+ :page-id page-id
+ :id s-id-1
+ :parent-id uuid/zero
+ :frame-id uuid/zero
+ :components-v2 true
+ :obj (cts/setup-shape
+ {:id s-id-1
+ :name "Board"
+ :frame-id uuid/zero
+ :parent-id uuid/zero
+ :type :frame
+ :main-instance true
+ :component-root true
+ :component-file (:id file)
+ :component-id c-id})}
+
+ {:type :add-obj
+ :page-id page-id
+ :id s-id-2
+ :parent-id uuid/zero
+ :frame-id uuid/zero
+ :components-v2 true
+ :obj (cts/setup-shape
+ {:id s-id-2
+ :name "Board"
+ :frame-id uuid/zero
+ :parent-id uuid/zero
+ :type :frame
+ :main-instance false
+ :component-root true
+ :component-file (:id file)
+ :component-id c-id})}
+
+ {:type :add-component
+ :path ""
+ :name "Board"
+ :main-instance-id s-id-1
+ :main-instance-page page-id
+ :id c-id
+ :anotation nil}])
+
+ ;; Run the file-gc task immediately without forced min-age
+ (t/is (false? (th/run-task! :file-gc {:file-id (:id file)})))
+
+ ;; Run the task again
+ (t/is (true? (th/run-task! :file-gc {:min-age 0 :file-id (:id file)})))
+
+ ;; Retrieve file and check trimmed attribute
+ (let [row (th/db-get :file {:id (:id file)})]
+ (t/is (true? (:has-media-trimmed row))))
+
+ ;; Check that component exists
+ (let [data {::th/type :get-file
+ ::rpc/profile-id (:id profile)
+ :id (:id file)}
+ out (th/command! data)]
+
+ (t/is (th/success? out))
+ (let [result (:result out)
+ component (get-in result [:data :components c-id])]
+
+ (t/is (some? component))
+ (t/is (nil? (:objects component)))))
+
+ ;; Now proceed to delete a component
+ (update-file!
+ :file-id (:id file)
+ :profile-id (:id profile)
+ :revn 0
+ :vern 0
+ :changes
+ [{:type :del-component
+ :id c-id}
+ {:type :del-obj
+ :page-id page-id
+ :id s-id-1
+ :ignore-touched true}])
+
+ ;; ;; Check that component is marked as deleted
+ (let [data {::th/type :get-file
+ ::rpc/profile-id (:id profile)
+ :id (:id file)}
+ out (th/command! data)]
+
+ (t/is (th/success? out))
+ (let [result (:result out)
+ component (get-in result [:data :components c-id])]
+ (t/is (true? (:deleted component)))
+ (t/is (some? (not-empty (:objects component))))))
+
+ ;; Re-run the file-gc task
+ (t/is (true? (th/run-task! :file-gc {:min-age 0 :file-id (:id file)})))
+ (let [row (th/db-get :file {:id (:id file)})]
+ (t/is (true? (:has-media-trimmed row))))
+
+ ;; Check that component is still there after file-gc task
+ (let [data {::th/type :get-file
+ ::rpc/profile-id (:id profile)
+ :id (:id file)}
+ out (th/command! data)]
+
+ (t/is (th/success? out))
+ (let [result (:result out)
+ component (get-in result [:data :components c-id])]
+ (t/is (true? (:deleted component)))
+ (t/is (some? (not-empty (:objects component))))))
+
+ ;; Now delete the last instance using deleted component
+ (update-file!
+ :file-id (:id file)
+ :profile-id (:id profile)
+ :revn 0
+ :vern 0
+ :changes
+ [{:type :del-obj
+ :page-id page-id
+ :id s-id-2
+ :ignore-touched true}])
+
+ ;; Now, we have deleted the usage of component if we pass file-gc,
+ ;; that component should be deleted
+ (t/is (true? (th/run-task! :file-gc {:min-age 0 :file-id (:id file)})))
+
+ ;; Check that component is properly removed
+ (let [data {::th/type :get-file
+ ::rpc/profile-id (:id profile)
+ :id (:id file)}
+ out (th/command! data)]
+
+ (t/is (th/success? out))
+ (let [result (:result out)
+ components (get-in result [:data :components])]
+ (t/is (not (contains? components c-id)))))))
+
+(t/deftest file-gc-with-components-2
+ (let [storage (:app.storage/storage th/*system*)
+ profile (th/create-profile* 1)
+ file-1 (th/create-file* 1 {:profile-id (:id profile)
+ :project-id (:default-project-id profile)
+ :is-shared true})
+
+ file-2 (th/create-file* 2 {:profile-id (:id profile)
+ :project-id (:default-project-id profile)
+ :is-shared false})
+
+ rel (th/link-file-to-library*
+ {:file-id (:id file-2)
+ :library-id (:id file-1)})
+
+ s-id-1 (uuid/random)
+ s-id-2 (uuid/random)
+ c-id (uuid/random)
+
+ f1-page-id (first (get-in file-1 [:data :pages]))
+ f2-page-id (first (get-in file-2 [:data :pages]))]
+
+ ;; Update file library inserting new component
+ (update-file!
+ :file-id (:id file-1)
+ :profile-id (:id profile)
+ :revn 0
+ :vern 0
+ :changes
+ [{:type :add-obj
+ :page-id f1-page-id
+ :id s-id-1
+ :parent-id uuid/zero
+ :frame-id uuid/zero
+ :components-v2 true
+ :obj (cts/setup-shape
+ {:id s-id-1
+ :name "Board"
+ :frame-id uuid/zero
+ :parent-id uuid/zero
+ :type :frame
+ :main-instance true
+ :component-root true
+ :component-file (:id file-1)
+ :component-id c-id})}
+ {:type :add-component
+ :path ""
+ :name "Board"
+ :main-instance-id s-id-1
+ :main-instance-page f1-page-id
+ :id c-id
+ :anotation nil}])
+
+ ;; Instanciate a component in a different file
+ (update-file!
+ :file-id (:id file-2)
+ :profile-id (:id profile)
+ :revn 0
+ :vern 0
+ :changes
+ [{:type :add-obj
+ :page-id f2-page-id
+ :id s-id-2
+ :parent-id uuid/zero
+ :frame-id uuid/zero
+ :components-v2 true
+ :obj (cts/setup-shape
+ {:id s-id-2
+ :name "Board"
+ :frame-id uuid/zero
+ :parent-id uuid/zero
+ :type :frame
+ :main-instance false
+ :component-root true
+ :component-file (:id file-1)
+ :component-id c-id})}])
+
+ ;; Run the file-gc on file and library
+ (t/is (true? (th/run-task! :file-gc {:min-age 0 :file-id (:id file-1)})))
+ (t/is (true? (th/run-task! :file-gc {:min-age 0 :file-id (:id file-2)})))
+
+ ;; Check that component exists
+ (let [data {::th/type :get-file
+ ::rpc/profile-id (:id profile)
+ :id (:id file-1)}
+ out (th/command! data)]
+
+ (t/is (th/success? out))
+ (let [result (:result out)
+ component (get-in result [:data :components c-id])]
+
+ (t/is (some? component))
+ (t/is (nil? (:objects component)))))
+
+ ;; Now proceed to delete a component
+ (update-file!
+ :file-id (:id file-1)
+ :profile-id (:id profile)
+ :revn 0
+ :vern 0
+ :changes
+ [{:type :del-component
+ :id c-id}
+ {:type :del-obj
+ :page-id f1-page-id
+ :id s-id-1
+ :ignore-touched true}])
+
+ ;; Check that component is marked as deleted
+ (let [data {::th/type :get-file
+ ::rpc/profile-id (:id profile)
+ :id (:id file-1)}
+ out (th/command! data)]
+
+ (t/is (th/success? out))
+ (let [result (:result out)
+ component (get-in result [:data :components c-id])]
+ (t/is (true? (:deleted component)))
+ (t/is (some? (not-empty (:objects component))))))
+
+ ;; Re-run the file-gc task
+ (t/is (true? (th/run-task! :file-gc {:min-age 0 :file-id (:id file-1)})))
+ (t/is (false? (th/run-task! :file-gc {:min-age 0 :file-id (:id file-2)})))
+
+ ;; Check that component is still there after file-gc task
+ (let [data {::th/type :get-file
+ ::rpc/profile-id (:id profile)
+ :id (:id file-1)}
+ out (th/command! data)]
+
+ (t/is (th/success? out))
+ (let [result (:result out)
+ component (get-in result [:data :components c-id])]
+ (t/is (true? (:deleted component)))
+ (t/is (some? (not-empty (:objects component))))))
+
+ ;; Now delete the last instance using deleted component
+ (update-file!
+ :file-id (:id file-2)
+ :profile-id (:id profile)
+ :revn 0
+ :vern 0
+ :changes
+ [{:type :del-obj
+ :page-id f2-page-id
+ :id s-id-2
+ :ignore-touched true}])
+
+ ;; Mark
+ (th/db-exec! ["update file set has_media_trimmed = false where id = ?" (:id file-1)])
+
+ ;; Now, we have deleted the usage of component if we pass file-gc,
+ ;; that component should be deleted
+ (t/is (true? (th/run-task! :file-gc {:min-age 0 :file-id (:id file-1)})))
+
+ ;; Check that component is properly removed
+ (let [data {::th/type :get-file
+ ::rpc/profile-id (:id profile)
+ :id (:id file-1)}
+ out (th/command! data)]
+
+ (t/is (th/success? out))
+ (let [result (:result out)
+ components (get-in result [:data :components])]
+ (t/is (not (contains? components c-id)))))))
+
From 066b1235a642fb3ae20eff791b8a1fa12d90670d Mon Sep 17 00:00:00 2001
From: Andrey Antukh
Date: Mon, 20 Jan 2025 23:15:33 +0100
Subject: [PATCH 23/26] :bug: Pass correct default options on db/plan fn
---
backend/src/app/db.clj | 10 ++++------
backend/src/app/db/sql.clj | 9 +++++----
backend/src/app/rpc/commands/management.clj | 2 +-
3 files changed, 10 insertions(+), 11 deletions(-)
diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj
index b22ecd899d..128c3f44e6 100644
--- a/backend/src/app/db.clj
+++ b/backend/src/app/db.clj
@@ -270,19 +270,17 @@
:else (throw (IllegalArgumentException. "unable to resolve connectable"))))
(def ^:private params-mapping
- {::return-keys? :return-keys
- ::return-keys :return-keys})
+ {::return-keys :return-keys})
(defn rename-opts
[opts]
(set/rename-keys opts params-mapping))
(def ^:private default-insert-opts
- {:builder-fn sql/as-kebab-maps
- :return-keys true})
+ (assoc sql/default-opts :return-keys true))
(def ^:private default-opts
- {:builder-fn sql/as-kebab-maps})
+ sql/default-opts)
(defn exec!
([ds sv] (exec! ds sv nil))
@@ -333,7 +331,7 @@
(defn update!
"A helper that build an UPDATE SQL statement and executes it.
- Given a connectable object, a table name, a hash map of columns and
+ Given a connectable object, a table name, a hash map of columns and
values to set, and either a hash map of columns and values to search
on or a vector of a SQL where clause and parameters, perform an
update on the table.
diff --git a/backend/src/app/db/sql.clj b/backend/src/app/db/sql.clj
index 42641039e3..beabb8861e 100644
--- a/backend/src/app/db/sql.clj
+++ b/backend/src/app/db/sql.clj
@@ -15,14 +15,15 @@
(defn kebab-case [s] (str/replace s #"_" "-"))
(defn snake-case [s] (str/replace s #"-" "_"))
-(def default-opts
- {:table-fn snake-case
- :column-fn snake-case})
-
(defn as-kebab-maps
[rs opts]
(jdbc-opt/as-unqualified-modified-maps rs (assoc opts :label-fn kebab-case)))
+(def default-opts
+ {:table-fn snake-case
+ :column-fn snake-case
+ :builder-fn as-kebab-maps})
+
(defn insert
([table key-map]
(insert table key-map nil))
diff --git a/backend/src/app/rpc/commands/management.clj b/backend/src/app/rpc/commands/management.clj
index cad94b019e..16894f4e6f 100644
--- a/backend/src/app/rpc/commands/management.clj
+++ b/backend/src/app/rpc/commands/management.clj
@@ -67,7 +67,7 @@
:is-owner true
:is-admin true
:can-edit true}
- {::db/return-keys? false}))
+ {::db/return-keys false}))
(doseq [params (sequence (comp
(map #(bfc/remap-id % :file-id))
From da0704081faeb60125d36c9b660c5dae3796762b Mon Sep 17 00:00:00 2001
From: Andrey Antukh
Date: Mon, 20 Jan 2025 23:21:41 +0100
Subject: [PATCH 24/26] :paperclip: Normalize default opts for db/plan function
---
backend/src/app/db.clj | 11 +++++++++--
backend/src/app/tasks/file_gc.clj | 10 ++--------
2 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj
index 128c3f44e6..625fe872fc 100644
--- a/backend/src/app/db.clj
+++ b/backend/src/app/db.clj
@@ -411,13 +411,20 @@
:hint "database object not found"))
row))
+(def ^:private default-plan-opts
+ (-> default-opts
+ (assoc :fetch-size 1)
+ (assoc :concurrency :read-only)
+ (assoc :cursors :close)
+ (assoc :result-type :forward-only)))
+
(defn plan
([ds sql]
(-> (get-connectable ds)
- (jdbc/plan sql default-opts)))
+ (jdbc/plan sql default-plan-opts)))
([ds sql opts]
(-> (get-connectable ds)
- (jdbc/plan sql (merge default-opts opts)))))
+ (jdbc/plan sql (merge default-plan-opts opts)))))
(defn cursor
"Return a lazy seq of rows using server side cursors"
diff --git a/backend/src/app/tasks/file_gc.clj b/backend/src/app/tasks/file_gc.clj
index 7bbdfa07e1..30f6c2f5ae 100644
--- a/backend/src/app/tasks/file_gc.clj
+++ b/backend/src/app/tasks/file_gc.clj
@@ -56,12 +56,6 @@
(map :data)
(mapcat bfc/collect-used-media)))
-(def ^:private plan-opts
- {:fetch-size 1
- :concurrency :read-only
- :cursors :close
- :result-type :forward-only})
-
(defn- clean-file-media!
"Performs the garbage collection of file media objects."
[{:keys [::db/conn] :as cfg} {:keys [id] :as file}]
@@ -69,7 +63,7 @@
(map (partial decode-file cfg))
xf:collect-used-media)
- used (->> (db/plan conn [sql:get-snapshots id] plan-opts)
+ used (->> (db/plan conn [sql:get-snapshots id])
(transduce xform conj #{}))
used (into used xf:collect-used-media [file])
@@ -175,7 +169,7 @@
(mapcat (partial get-used-components deleted-components file-id))
used-remote
- (->> (db/plan conn [sql:get-files-for-library file-id] plan-opts)
+ (->> (db/plan conn [sql:get-files-for-library file-id])
(transduce (comp (map (partial decode-file cfg)) xform) conj #{}))
used-local
From 15157c54b1315b487bdf9498fe400cd358fdb7a5 Mon Sep 17 00:00:00 2001
From: Pablo Alba
Date: Wed, 22 Jan 2025 16:05:50 +0100
Subject: [PATCH 25/26] :bug: Fix shape-ref cycles
---
CHANGES.md | 1 +
common/src/app/common/files/repair.cljc | 29 ++++++++++++++++
common/src/app/common/files/validate.cljc | 18 ++++++++--
common/src/app/common/types/file.cljc | 40 +++++++++++++----------
4 files changed, 68 insertions(+), 20 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index b3fc1a750f..79da857aeb 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -6,6 +6,7 @@
- Fix detach when top copy is dangling and nested copy is not [Taiga #9699](https://tree.taiga.io/project/penpot/issue/9699)
- Fix problem in plugins with `replaceColor` method [#174](https://github.com/penpot/penpot-plugins/issues/174)
+- Fix issue with recursive commponents [Taiga #9903](https://tree.taiga.io/project/penpot/issue/9903)
- Fix missing methods reference on API Docs
diff --git a/common/src/app/common/files/repair.cljc b/common/src/app/common/files/repair.cljc
index 67f90dafeb..5d80c844ff 100644
--- a/common/src/app/common/files/repair.cljc
+++ b/common/src/app/common/files/repair.cljc
@@ -320,6 +320,35 @@
(pcb/with-file-data file-data)
(pcb/update-shapes shape-ids detach-shape))))))
+
+(defmethod repair-error :shape-ref-cycle
+ [_ {:keys [shape args] :as error} file-data _]
+ (let [repair-component
+ (fn [component]
+ (let [objects (:objects component) ;; we only have encounter this on deleted components,
+ ;; so the relevant objects are inside the component
+ to-detach (->> (:cycles-ids args)
+ (map #(get objects %))
+ (map #(ctn/get-head-shape objects %))
+ (map :id)
+ distinct
+ (mapcat #(ctn/get-children-in-instance objects %))
+ (map :id)
+ set)]
+
+ (update component :objects
+ (fn [objects]
+ (reduce-kv (fn [acc k v]
+ (if (contains? to-detach k)
+ (assoc acc k (ctk/detach-shape v))
+ (assoc acc k v)))
+ {}
+ objects)))))]
+ (log/dbg :hint "repairing component :shape-ref-cycle" :id (:id shape) :name (:name shape))
+ (-> (pcb/empty-changes nil nil)
+ (pcb/with-library-data file-data)
+ (pcb/update-component (:id shape) repair-component))))
+
(defmethod repair-error :shape-ref-in-main
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
diff --git a/common/src/app/common/files/validate.cljc b/common/src/app/common/files/validate.cljc
index 79e6cf3017..f1f2bdda9e 100644
--- a/common/src/app/common/files/validate.cljc
+++ b/common/src/app/common/files/validate.cljc
@@ -55,7 +55,8 @@
:component-nil-objects-not-allowed
:instance-head-not-frame
:misplaced-slot
- :missing-slot})
+ :missing-slot
+ :shape-ref-cycle})
(def ^:private schema:error
[:map {:title "ValidationError"}
@@ -482,6 +483,18 @@
"This deleted component has children with the same swap slot"
component file nil))))
+(defn check-ref-cycles
+ [component file]
+ (let [cycles-ids (->> component
+ :objects
+ vals
+ (filter #(= (:id %) (:shape-ref %)))
+ (map :id))]
+
+ (when (seq cycles-ids)
+ (report-error :shape-ref-cycle
+ "This deleted component has shapes with shape-ref pointing to self"
+ component file nil :cycles-ids cycles-ids))))
(defn- check-component
"Validate semantic coherence of a component. Report all errors found."
@@ -491,7 +504,8 @@
"Objects list cannot be nil"
component file nil))
(when (:deleted component)
- (check-component-duplicate-swap-slot component file)))
+ (check-component-duplicate-swap-slot component file)
+ (check-ref-cycles component file)))
(defn- get-orphan-shapes
[{:keys [objects] :as page}]
diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc
index 8236631ef8..9b575179aa 100644
--- a/common/src/app/common/types/file.cljc
+++ b/common/src/app/common/types/file.cljc
@@ -335,24 +335,28 @@
(true? (= (:id component) (:id ref-component)))))
(defn find-swap-slot
- [shape container file libraries]
- (if-let [swap-slot (ctk/get-swap-slot shape)]
- swap-slot
- (let [ref-shape (find-ref-shape file
- container
- libraries
- shape
- :include-deleted? true
- :with-context? true)
- shape-meta (meta ref-shape)
- ref-file (:file shape-meta)
- ref-container (:container shape-meta)]
- (when ref-shape
- (if-let [swap-slot (ctk/get-swap-slot ref-shape)]
- swap-slot
- (if (ctk/main-instance? ref-shape)
- (:id shape)
- (find-swap-slot ref-shape ref-container ref-file libraries)))))))
+ ([shape container file libraries]
+ (find-swap-slot shape container file libraries #{}))
+ ([shape container file libraries viewed-ids]
+ (if (contains? viewed-ids (:id shape)) ;; prevent cycles
+ nil
+ (if-let [swap-slot (ctk/get-swap-slot shape)]
+ swap-slot
+ (let [ref-shape (find-ref-shape file
+ container
+ libraries
+ shape
+ :include-deleted? true
+ :with-context? true)
+ shape-meta (meta ref-shape)
+ ref-file (:file shape-meta)
+ ref-container (:container shape-meta)]
+ (when ref-shape
+ (if-let [swap-slot (ctk/get-swap-slot ref-shape)]
+ swap-slot
+ (if (ctk/main-instance? ref-shape)
+ (:id shape)
+ (find-swap-slot ref-shape ref-container ref-file libraries (conj viewed-ids (:id shape)))))))))))
(defn match-swap-slot?
[shape-main shape-inst container-inst container-main file libraries]
From 7ca98ddf21bbc6b704773ecf0e62a9dc65e2ee2f Mon Sep 17 00:00:00 2001
From: Andrey Antukh
Date: Wed, 22 Jan 2025 16:07:07 +0100
Subject: [PATCH 26/26] :paperclip: Add missing entry on changelog
---
CHANGES.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGES.md b/CHANGES.md
index 79da857aeb..ef56d959df 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -8,7 +8,7 @@
- Fix problem in plugins with `replaceColor` method [#174](https://github.com/penpot/penpot-plugins/issues/174)
- Fix issue with recursive commponents [Taiga #9903](https://tree.taiga.io/project/penpot/issue/9903)
- Fix missing methods reference on API Docs
-
+- Fix memory usage issue on file-gc asynchronous task (related to snapshots feature)
## 2.4.1