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.
Team members

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.
Team members

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.
Team members

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